source: branches/2.2/jabberit_messenger/jmessenger/js/strophe.js @ 3261

Revision 3261, 115.3 KB checked in by alexandrecorreia, 14 years ago (diff)

Ticket #1316 - Correcoes e melhorias na busca dos contatos para o Modulo IM.

  • Property svn:executable set to *
Line 
1// This code was written by Tyler Akins and has been placed in the
2// public domain.  It would be nice if you left this header intact.
3// Base64 code from Tyler Akins -- http://rumkin.com
4
5var Base64 = (function () {
6    var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
7
8    var obj = {
9        /**
10         * Encodes a string in base64
11         * @param {String} input The string to encode in base64.
12         */
13        encode: function (input) {
14            var output = "";
15            var chr1, chr2, chr3;
16            var enc1, enc2, enc3, enc4;
17            var i = 0;
18       
19            do {
20                chr1 = input.charCodeAt(i++);
21                chr2 = input.charCodeAt(i++);
22                chr3 = input.charCodeAt(i++);
23               
24                enc1 = chr1 >> 2;
25                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
26                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
27                enc4 = chr3 & 63;
28
29                if (isNaN(chr2)) {
30                    enc3 = enc4 = 64;
31                } else if (isNaN(chr3)) {
32                    enc4 = 64;
33                }
34               
35                output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) +
36                    keyStr.charAt(enc3) + keyStr.charAt(enc4);
37            } while (i < input.length);
38           
39            return output;
40        },
41       
42        /**
43         * Decodes a base64 string.
44         * @param {String} input The string to decode.
45         */
46        decode: function (input) {
47            var output = "";
48            var chr1, chr2, chr3;
49            var enc1, enc2, enc3, enc4;
50            var i = 0;
51           
52            // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
53            input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
54           
55            do {
56                enc1 = keyStr.indexOf(input.charAt(i++));
57                enc2 = keyStr.indexOf(input.charAt(i++));
58                enc3 = keyStr.indexOf(input.charAt(i++));
59                enc4 = keyStr.indexOf(input.charAt(i++));
60               
61                chr1 = (enc1 << 2) | (enc2 >> 4);
62                chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
63                chr3 = ((enc3 & 3) << 6) | enc4;
64               
65                output = output + String.fromCharCode(chr1);
66               
67                if (enc3 != 64) {
68                    output = output + String.fromCharCode(chr2);
69                }
70                if (enc4 != 64) {
71                    output = output + String.fromCharCode(chr3);
72                }
73            } while (i < input.length);
74           
75            return output;
76        }
77    };
78
79    return obj;
80})();
81/*
82 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
83 * Digest Algorithm, as defined in RFC 1321.
84 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
85 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
86 * Distributed under the BSD License
87 * See http://pajhome.org.uk/crypt/md5 for more info.
88 */
89
90var MD5 = (function () {
91    /*
92     * Configurable variables. You may need to tweak these to be compatible with
93     * the server-side, but the defaults work in most cases.
94     */
95    var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase */
96    var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance */
97    var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode */
98
99    /*
100     * Add integers, wrapping at 2^32. This uses 16-bit operations internally
101     * to work around bugs in some JS interpreters.
102     */
103    var safe_add = function (x, y) {
104        var lsw = (x & 0xFFFF) + (y & 0xFFFF);
105        var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
106        return (msw << 16) | (lsw & 0xFFFF);
107    };
108
109    /*
110     * Bitwise rotate a 32-bit number to the left.
111     */
112    var bit_rol = function (num, cnt) {
113        return (num << cnt) | (num >>> (32 - cnt));
114    };
115
116    /*
117     * Convert a string to an array of little-endian words
118     * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
119     */
120    var str2binl = function (str) {
121        var bin = [];
122        var mask = (1 << chrsz) - 1;
123        for(var i = 0; i < str.length * chrsz; i += chrsz)
124        {
125            bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
126        }
127        return bin;
128    };
129
130    /*
131     * Convert an array of little-endian words to a string
132     */
133    var binl2str = function (bin) {
134        var str = "";
135        var mask = (1 << chrsz) - 1;
136        for(var i = 0; i < bin.length * 32; i += chrsz)
137        {
138            str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
139        }
140        return str;
141    };
142
143    /*
144     * Convert an array of little-endian words to a hex string.
145     */
146    var binl2hex = function (binarray) {
147        var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
148        var str = "";
149        for(var i = 0; i < binarray.length * 4; i++)
150        {
151            str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
152                hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
153        }
154        return str;
155    };
156
157    /*
158     * Convert an array of little-endian words to a base-64 string
159     */
160    var binl2b64 = function (binarray) {
161        var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
162        var str = "";
163        var triplet, j;
164        for(var i = 0; i < binarray.length * 4; i += 3)
165        {
166            triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16) |
167                (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) |
168                ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
169            for(j = 0; j < 4; j++)
170            {
171                if(i * 8 + j * 6 > binarray.length * 32) { str += b64pad; }
172                else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); }
173            }
174        }
175        return str;
176    };
177
178    /*
179     * These functions implement the four basic operations the algorithm uses.
180     */
181    var md5_cmn = function (q, a, b, x, s, t) {
182        return safe_add(bit_rol(safe_add(safe_add(a, q),safe_add(x, t)), s),b);
183    };
184
185    var md5_ff = function (a, b, c, d, x, s, t) {
186        return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
187    };
188
189    var md5_gg = function (a, b, c, d, x, s, t) {
190        return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
191    };
192
193    var md5_hh = function (a, b, c, d, x, s, t) {
194        return md5_cmn(b ^ c ^ d, a, b, x, s, t);
195    };
196
197    var md5_ii = function (a, b, c, d, x, s, t) {
198        return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
199    };
200   
201    /*
202     * Calculate the MD5 of an array of little-endian words, and a bit length
203     */
204    var core_md5 = function (x, len) {
205        /* append padding */
206        x[len >> 5] |= 0x80 << ((len) % 32);
207        x[(((len + 64) >>> 9) << 4) + 14] = len;
208
209        var a =  1732584193;
210        var b = -271733879;
211        var c = -1732584194;
212        var d =  271733878;
213
214        var olda, oldb, oldc, oldd;
215        for (var i = 0; i < x.length; i += 16)
216        {
217            olda = a;
218            oldb = b;
219            oldc = c;
220            oldd = d;
221           
222            a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
223            d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
224            c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
225            b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
226            a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
227            d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
228            c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
229            b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
230            a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
231            d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
232            c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
233            b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
234            a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
235            d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
236            c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
237            b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
238           
239            a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
240            d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
241            c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
242            b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
243            a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
244            d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
245            c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
246            b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
247            a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
248            d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
249            c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
250            b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
251            a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
252            d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
253            c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
254            b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
255           
256            a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
257            d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
258            c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
259            b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
260            a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
261            d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
262            c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
263            b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
264            a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
265            d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
266            c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
267            b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
268            a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
269            d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
270            c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
271            b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
272           
273            a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
274            d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
275            c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
276            b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
277            a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
278            d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
279            c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
280            b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
281            a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
282            d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
283            c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
284            b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
285            a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
286            d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
287            c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
288            b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
289           
290            a = safe_add(a, olda);
291            b = safe_add(b, oldb);
292            c = safe_add(c, oldc);
293            d = safe_add(d, oldd);
294        }
295        return [a, b, c, d];
296    };
297
298
299    /*
300     * Calculate the HMAC-MD5, of a key and some data
301     */
302    var core_hmac_md5 = function (key, data) {
303        var bkey = str2binl(key);
304        if(bkey.length > 16) { bkey = core_md5(bkey, key.length * chrsz); }
305       
306        var ipad = new Array(16), opad = new Array(16);
307        for(var i = 0; i < 16; i++)
308        {
309            ipad[i] = bkey[i] ^ 0x36363636;
310            opad[i] = bkey[i] ^ 0x5C5C5C5C;
311        }
312       
313        var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
314        return core_md5(opad.concat(hash), 512 + 128);
315    };
316
317    var obj = {
318        /*
319         * These are the functions you'll usually want to call.
320         * They take string arguments and return either hex or base-64 encoded
321         * strings.
322         */
323        hexdigest: function (s) {
324            return binl2hex(core_md5(str2binl(s), s.length * chrsz));
325        },
326
327        b64digest: function (s) {
328            return binl2b64(core_md5(str2binl(s), s.length * chrsz));
329        },
330
331        hash: function (s) {
332            return binl2str(core_md5(str2binl(s), s.length * chrsz));
333        },
334
335        hmac_hexdigest: function (key, data) {
336            return binl2hex(core_hmac_md5(key, data));
337        },
338
339        hmac_b64digest: function (key, data) {
340            return binl2b64(core_hmac_md5(key, data));
341        },
342
343        hmac_hash: function (key, data) {
344            return binl2str(core_hmac_md5(key, data));
345        },
346
347        /*
348         * Perform a simple self-test to see if the VM is working
349         */
350        test: function () {
351            return MD5.hexdigest("abc") === "900150983cd24fb0d6963f7d28e17f72";
352        }
353    };
354
355    return obj;
356})();
357
358/*
359    This program is distributed under the terms of the MIT license.
360    Please see the LICENSE file for details.
361
362    Copyright 2006-2008, OGG, LLC
363*/
364
365/* jslint configuration: */
366/*global document, window, setTimeout, clearTimeout, console,
367    XMLHttpRequest, ActiveXObject,
368    Base64, MD5,
369    Strophe, $build, $msg, $iq, $pres */
370
371/** File: strophe.js
372 *  A JavaScript library for XMPP BOSH.
373 *
374 *  This is the JavaScript version of the Strophe library.  Since JavaScript
375 *  has no facilities for persistent TCP connections, this library uses
376 *  Bidirectional-streams Over Synchronous HTTP (BOSH) to emulate
377 *  a persistent, stateful, two-way connection to an XMPP server.  More
378 *  information on BOSH can be found in XEP 124.
379 */
380
381/** PrivateFunction: Function.prototype.bind
382 *  Bind a function to an instance.
383 *
384 *  This Function object extension method creates a bound method similar
385 *  to those in Python.  This means that the 'this' object will point
386 *  to the instance you want.  See
387 *  <a href='http://benjamin.smedbergs.us/blog/2007-01-03/bound-functions-and-function-imports-in-javascript/'>Bound Functions and Function Imports in JavaScript</a>
388 *  for a complete explanation.
389 *
390 *  This extension already exists in some browsers (namely, Firefox 3), but
391 *  we provide it to support those that don't.
392 *
393 *  Parameters:
394 *    (Object) obj - The object that will become 'this' in the bound function.
395 *
396 *  Returns:
397 *    The bound function.
398 */
399if (!Function.prototype.bind) {
400    Function.prototype.bind = function (obj)
401    {
402        var func = this;
403        return function () { return func.apply(obj, arguments); };
404    };
405}
406
407/** PrivateFunction: Function.prototype.prependArg
408 *  Prepend an argument to a function.
409 *
410 *  This Function object extension method returns a Function that will
411 *  invoke the original function with an argument prepended.  This is useful
412 *  when some object has a callback that needs to get that same object as
413 *  an argument.  The following fragment illustrates a simple case of this
414 *  > var obj = new Foo(this.someMethod);</code></blockquote>
415 *
416 *  Foo's constructor can now use func.prependArg(this) to ensure the
417 *  passed in callback function gets the instance of Foo as an argument.
418 *  Doing this without prependArg would mean not setting the callback
419 *  from the constructor.
420 *
421 *  This is used inside Strophe for passing the Strophe.Request object to
422 *  the onreadystatechange handler of XMLHttpRequests.
423 *
424 *  Parameters:
425 *    arg - The argument to pass as the first parameter to the function.
426 *
427 *  Returns:
428 *    A new Function which calls the original with the prepended argument.
429 */
430if (!Function.prototype.prependArg) {
431    Function.prototype.prependArg = function (arg)
432    {
433        var func = this;
434
435        return function () {
436            var newargs = [arg];
437            for (var i = 0; i < arguments.length; i++) {
438                newargs.push(arguments[i]);
439            }
440            return func.apply(this, newargs);
441        };
442    };
443}
444
445/** PrivateFunction: Array.prototype.indexOf
446 *  Return the index of an object in an array.
447 *
448 *  This function is not supplied by some JavaScript implementations, so
449 *  we provide it if it is missing.  This code is from:
450 *  http://developer.mozilla.org/En/Core_JavaScript_1.5_Reference:Objects:Array:indexOf
451 *
452 *  Parameters:
453 *    (Object) elt - The object to look for.
454 *    (Integer) from - The index from which to start looking. (optional).
455 *
456 *  Returns:
457 *    The index of elt in the array or -1 if not found.
458 */
459
460/*
461if (!Array.prototype.indexOf)
462{
463    Array.prototype.indexOf = function( elt )
464    {
465        var len = this.length;
466
467        var from = Number(arguments[1]) || 0;
468        from = (from < 0) ? Math.ceil(from) : Math.floor(from);
469        if (from < 0) {
470            from += len;
471        }
472
473        for (; from < len; from++) {
474            if (from in this && this[from] === elt) {
475                return from;
476            }
477        }
478
479        return -1;
480    };
481}
482*/
483
484/* All of the Strophe globals are defined in this special function below so
485 * that references to the globals become closures.  This will ensure that
486 * on page reload, these references will still be available to callbacks
487 * that are still executing.
488 */
489
490(function (callback) {
491var Strophe;
492
493/** Function: $build
494 *  Create a Strophe.Builder.
495 *  This is an alias for 'new Strophe.Builder(name, attrs)'.
496 *
497 *  Parameters:
498 *    (String) name - The root element name.
499 *    (Object) attrs - The attributes for the root element in object notation.
500 *
501 *  Returns:
502 *    A new Strophe.Builder object.
503 */
504function $build(name, attrs) { return new Strophe.Builder(name, attrs); }
505/** Function: $msg
506 *  Create a Strophe.Builder with a <message/> element as the root.
507 *
508 *  Parmaeters:
509 *    (Object) attrs - The <message/> element attributes in object notation.
510 *
511 *  Returns:
512 *    A new Strophe.Builder object.
513 */
514function $msg(attrs) { return new Strophe.Builder("message", attrs); }
515/** Function: $iq
516 *  Create a Strophe.Builder with an <iq/> element as the root.
517 *
518 *  Parameters:
519 *    (Object) attrs - The <iq/> element attributes in object notation.
520 *
521 *  Returns:
522 *    A new Strophe.Builder object.
523 */
524function $iq(attrs) { return new Strophe.Builder("iq", attrs); }
525/** Function: $pres
526 *  Create a Strophe.Builder with a <presence/> element as the root.
527 *
528 *  Parameters:
529 *    (Object) attrs - The <presence/> element attributes in object notation.
530 *
531 *  Returns:
532 *    A new Strophe.Builder object.
533 */
534function $pres(attrs) { return new Strophe.Builder("presence", attrs); }
535
536/** Class: Strophe
537 *  An object container for all Strophe library functions.
538 *
539 *  This class is just a container for all the objects and constants
540 *  used in the library.  It is not meant to be instantiated, but to
541 *  provide a namespace for library objects, constants, and functions.
542 */
543Strophe = {
544    /** Constant: VERSION
545     *  The version of the Strophe library. Unreleased builds will have
546     *  a version of head-HASH where HASH is a partial revision.
547     */
548    VERSION: "1.0.1",
549
550    /** Constants: XMPP Namespace Constants
551     *  Common namespace constants from the XMPP RFCs and XEPs.
552     *
553     *  NS.HTTPBIND - HTTP BIND namespace from XEP 124.
554     *  NS.BOSH - BOSH namespace from XEP 206.
555     *  NS.CLIENT - Main XMPP client namespace.
556     *  NS.AUTH - Legacy authentication namespace.
557     *  NS.ROSTER - Roster operations namespace.
558     *  NS.PROFILE - Profile namespace.
559     *  NS.DISCO_INFO - Service discovery info namespace from XEP 30.
560     *  NS.DISCO_ITEMS - Service discovery items namespace from XEP 30.
561     *  NS.MUC - Multi-User Chat namespace from XEP 45.
562     *  NS.SASL - XMPP SASL namespace from RFC 3920.
563     *  NS.STREAM - XMPP Streams namespace from RFC 3920.
564     *  NS.BIND - XMPP Binding namespace from RFC 3920.
565     *  NS.SESSION - XMPP Session namespace from RFC 3920.
566     */
567    NS: {
568        HTTPBIND: "http://jabber.org/protocol/httpbind",
569        BOSH: "urn:xmpp:xbosh",
570        CLIENT: "jabber:client",
571        AUTH: "jabber:iq:auth",
572        ROSTER: "jabber:iq:roster",
573        PROFILE: "jabber:iq:profile",
574        DISCO_INFO: "http://jabber.org/protocol/disco#info",
575        DISCO_ITEMS: "http://jabber.org/protocol/disco#items",
576        MUC: "http://jabber.org/protocol/muc",
577        MUC_USER: "http://jabber.org/protocol/muc#user",
578        SASL: "urn:ietf:params:xml:ns:xmpp-sasl",
579        STREAM: "http://etherx.jabber.org/streams",
580        BIND: "urn:ietf:params:xml:ns:xmpp-bind",
581        SESSION: "urn:ietf:params:xml:ns:xmpp-session",
582        VERSION: "jabber:iq:version",
583        STANZAS: "urn:ietf:params:xml:ns:xmpp-stanzas"
584    },
585
586    /** Function: addNamespace
587     *  This function is used to extend the current namespaces in
588     *  Strophe.NS.  It takes a key and a value with the key being the
589     *  name of the new namespace, with its actual value.
590     *  For example:
591     *  Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub");
592     *
593     *  Parameters:
594     *    (String) name - The name under which the namespace will be
595     *      referenced under Strophe.NS
596     *    (String) value - The actual namespace.       
597     */
598    addNamespace: function (name, value)
599    {
600        Strophe.NS[name] = value;
601    },
602
603    /** Constants: Connection Status Constants
604     *  Connection status constants for use by the connection handler
605     *  callback.
606     *
607     *  Status.ERROR - An error has occurred
608     *  Status.CONNECTING - The connection is currently being made
609     *  Status.CONNFAIL - The connection attempt failed
610     *  Status.AUTHENTICATING - The connection is authenticating
611     *  Status.AUTHFAIL - The authentication attempt failed
612     *  Status.CONNECTED - The connection has succeeded
613     *  Status.DISCONNECTED - The connection has been terminated
614     *  Status.DISCONNECTING - The connection is currently being terminated
615     *  Status.ATTACHED - The connection has been attached
616     */
617    Status: {
618        ERROR: 0,
619        CONNECTING: 1,
620        CONNFAIL: 2,
621        AUTHENTICATING: 3,
622        AUTHFAIL: 4,
623        CONNECTED: 5,
624        DISCONNECTED: 6,
625        DISCONNECTING: 7,
626        ATTACHED: 8
627    },
628
629    /** Constants: Log Level Constants
630     *  Logging level indicators.
631     *
632     *  LogLevel.DEBUG - Debug output
633     *  LogLevel.INFO - Informational output
634     *  LogLevel.WARN - Warnings
635     *  LogLevel.ERROR - Errors
636     *  LogLevel.FATAL - Fatal errors
637     */
638    LogLevel: {
639        DEBUG: 0,
640        INFO: 1,
641        WARN: 2,
642        ERROR: 3,
643        FATAL: 4
644    },
645
646    /** PrivateConstants: DOM Element Type Constants
647     *  DOM element types.
648     *
649     *  ElementType.NORMAL - Normal element.
650     *  ElementType.TEXT - Text data element.
651     */
652    ElementType: {
653        NORMAL: 1,
654        TEXT: 3
655    },
656
657    /** PrivateConstants: Timeout Values
658     *  Timeout values for error states.  These values are in seconds.
659     *  These should not be changed unless you know exactly what you are
660     *  doing.
661     *
662     *  TIMEOUT - Timeout multiplier. A waiting request will be considered
663     *      failed after Math.floor(TIMEOUT * wait) seconds have elapsed.
664     *      This defaults to 1.1, and with default wait, 66 seconds.
665     *  SECONDARY_TIMEOUT - Secondary timeout multiplier. In cases where
666     *      Strophe can detect early failure, it will consider the request
667     *      failed if it doesn't return after
668     *      Math.floor(SECONDARY_TIMEOUT * wait) seconds have elapsed.
669     *      This defaults to 0.1, and with default wait, 6 seconds.
670     */
671    TIMEOUT: 1.1,
672    SECONDARY_TIMEOUT: 0.1,
673
674    /** Function: forEachChild
675     *  Map a function over some or all child elements of a given element.
676     *
677     *  This is a small convenience function for mapping a function over
678     *  some or all of the children of an element.  If elemName is null, all
679     *  children will be passed to the function, otherwise only children
680     *  whose tag names match elemName will be passed.
681     *
682     *  Parameters:
683     *    (XMLElement) elem - The element to operate on.
684     *    (String) elemName - The child element tag name filter.
685     *    (Function) func - The function to apply to each child.  This
686     *      function should take a single argument, a DOM element.
687     */
688    forEachChild: function (elem, elemName, func)
689    {
690        var i, childNode;
691
692        for (i = 0; i < elem.childNodes.length; i++) {
693            childNode = elem.childNodes[i];
694            if (childNode.nodeType == Strophe.ElementType.NORMAL &&
695                (!elemName || this.isTagEqual(childNode, elemName))) {
696                func(childNode);
697            }
698        }
699    },
700
701    /** Function: isTagEqual
702     *  Compare an element's tag name with a string.
703     *
704     *  This function is case insensitive.
705     *
706     *  Parameters:
707     *    (XMLElement) el - A DOM element.
708     *    (String) name - The element name.
709     *
710     *  Returns:
711     *    true if the element's tag name matches _el_, and false
712     *    otherwise.
713     */
714    isTagEqual: function (el, name)
715    {
716        return el.tagName.toLowerCase() == name.toLowerCase();
717    },
718
719    /** PrivateVariable: _xmlGenerator
720     *  _Private_ variable that caches a DOM document to
721     *  generate elements.
722     */
723    _xmlGenerator: null,
724
725    /** PrivateFunction: _makeGenerator
726     *  _Private_ function that creates a dummy XML DOM document to serve as
727     *  an element and text node generator.
728     */
729    _makeGenerator: function () {
730        var doc;
731
732        if (window.ActiveXObject) {
733            doc = new ActiveXObject("Microsoft.XMLDOM");
734            doc.appendChild(doc.createElement('strophe'));
735        } else {
736            doc = document.implementation
737                .createDocument('jabber:client', 'strophe', null);
738        }
739
740        return doc;
741    },
742
743    /** Function: xmlElement
744     *  Create an XML DOM element.
745     *
746     *  This function creates an XML DOM element correctly across all
747     *  implementations. Specifically the Microsoft implementation of
748     *  document.createElement makes DOM elements with 43+ default attributes
749     *  unless elements are created with the ActiveX object Microsoft.XMLDOM.
750     *
751     *  Most DOMs force element names to lowercase, so we use the
752     *  _realname attribute on the created element to store the case
753     *  sensitive name.  This is required to generate proper XML for
754     *  things like vCard avatars (XEP 153).  This attribute is stripped
755     *  out before being sent over the wire or serialized, but you may
756     *  notice it during debugging.
757     *
758     *  Parameters:
759     *    (String) name - The name for the element.
760     *    (Array) attrs - An optional array of key/value pairs to use as
761     *      element attributes in the following format [['key1', 'value1'],
762     *      ['key2', 'value2']]
763     *    (String) text - The text child data for the element.
764     *
765     *  Returns:
766     *    A new XML DOM element.
767     */
768    xmlElement: function (name)
769    {
770        if (!name) { return null; }
771
772        var node = null;
773        if (!Strophe._xmlGenerator) {
774            Strophe._xmlGenerator = Strophe._makeGenerator();
775        }
776        node = Strophe._xmlGenerator.createElement(name);
777
778        // FIXME: this should throw errors if args are the wrong type or
779        // there are more than two optional args
780        var a, i, k;
781        for (a = 1; a < arguments.length; a++) {
782            if (!arguments[a]) { continue; }
783            if (typeof(arguments[a]) == "string" ||
784                typeof(arguments[a]) == "number") {
785                node.appendChild(Strophe.xmlTextNode(arguments[a]));
786            } else if (typeof(arguments[a]) == "object" &&
787                       typeof(arguments[a].sort) == "function") {
788                for (i = 0; i < arguments[a].length; i++) {
789                    if (typeof(arguments[a][i]) == "object" &&
790                        typeof(arguments[a][i].sort) == "function") {
791                        node.setAttribute(arguments[a][i][0],
792                                          arguments[a][i][1]);
793                    }
794                }
795            } else if (typeof(arguments[a]) == "object") {
796                for (k in arguments[a]) {
797                    if (arguments[a].hasOwnProperty(k)) {
798                        node.setAttribute(k, arguments[a][k]);
799                    }
800                }
801            }
802        }
803
804        return node;
805    },
806
807    /*  Function: xmlescape
808     *  Excapes invalid xml characters.
809     *
810     *  Parameters:
811     *     (String) text - text to escape.
812     *
813     *  Returns:
814     *      Escaped text.
815     */
816    xmlescape: function(text)
817    {
818        text = text.replace(/\&/g, "&amp;");
819        text = text.replace(/</g,  "&lt;");
820        text = text.replace(/>/g,  "&gt;");
821        return text;   
822    },
823
824    /** Function: xmlTextNode
825     *  Creates an XML DOM text node.
826     *
827     *  Provides a cross implementation version of document.createTextNode.
828     *
829     *  Parameters:
830     *    (String) text - The content of the text node.
831     *
832     *  Returns:
833     *    A new XML DOM text node.
834     */
835    xmlTextNode: function (text)
836    {
837        //ensure text is escaped
838        text = Strophe.xmlescape(text);
839
840        if (!Strophe._xmlGenerator) {
841            Strophe._xmlGenerator = Strophe._makeGenerator();
842        }
843        return Strophe._xmlGenerator.createTextNode(text);
844    },
845
846    /** Function: getText
847     *  Get the concatenation of all text children of an element.
848     *
849     *  Parameters:
850     *    (XMLElement) elem - A DOM element.
851     *
852     *  Returns:
853     *    A String with the concatenated text of all text element children.
854     */
855    getText: function (elem)
856    {
857        if (!elem) { return null; }
858
859        var str = "";
860        if (elem.childNodes.length === 0 && elem.nodeType ==
861            Strophe.ElementType.TEXT) {
862            str += elem.nodeValue;
863        }
864
865        for (var i = 0; i < elem.childNodes.length; i++) {
866            if (elem.childNodes[i].nodeType == Strophe.ElementType.TEXT) {
867                str += elem.childNodes[i].nodeValue;
868            }
869        }
870
871        return str;
872    },
873
874    /** Function: copyElement
875     *  Copy an XML DOM element.
876     *
877     *  This function copies a DOM element and all its descendants and returns
878     *  the new copy.
879     *
880     *  Parameters:
881     *    (XMLElement) elem - A DOM element.
882     *
883     *  Returns:
884     *    A new, copied DOM element tree.
885     */
886    copyElement: function (elem)
887    {
888        var i, el;
889        if (elem.nodeType == Strophe.ElementType.NORMAL) {
890            el = Strophe.xmlElement(elem.tagName);
891
892            for (i = 0; i < elem.attributes.length; i++) {
893                el.setAttribute(elem.attributes[i].nodeName.toLowerCase(),
894                                elem.attributes[i].value);
895            }
896
897            for (i = 0; i < elem.childNodes.length; i++) {
898                el.appendChild(Strophe.copyElement(elem.childNodes[i]));
899            }
900        } else if (elem.nodeType == Strophe.ElementType.TEXT) {
901            el = Strophe.xmlTextNode(elem.nodeValue);
902        }
903
904        return el;
905    },
906
907    /** Function: escapeNode
908     *  Escape the node part (also called local part) of a JID.
909     *
910     *  Parameters:
911     *    (String) node - A node (or local part).
912     *
913     *  Returns:
914     *    An escaped node (or local part).
915     */
916    escapeNode: function (node)
917    {
918        return node.replace(/^\s+|\s+$/g, '')
919            .replace(/\\/g,  "\\5c")
920            .replace(/ /g,   "\\20")
921            .replace(/\"/g,  "\\22")
922            .replace(/\&/g,  "\\26")
923            .replace(/\'/g,  "\\27")
924            .replace(/\//g,  "\\2f")
925            .replace(/:/g,   "\\3a")
926            .replace(/</g,   "\\3c")
927            .replace(/>/g,   "\\3e")
928            .replace(/@/g,   "\\40");
929    },
930
931    /** Function: unescapeNode
932     *  Unescape a node part (also called local part) of a JID.
933     *
934     *  Parameters:
935     *    (String) node - A node (or local part).
936     *
937     *  Returns:
938     *    An unescaped node (or local part).
939     */
940    unescapeNode: function (node)
941    {
942        return node.replace(/\\20/g, " ")
943            .replace(/\\22/g, '"')
944            .replace(/\\26/g, "&")
945            .replace(/\\27/g, "'")
946            .replace(/\\2f/g, "/")
947            .replace(/\\3a/g, ":")
948            .replace(/\\3c/g, "<")
949            .replace(/\\3e/g, ">")
950            .replace(/\\40/g, "@")
951            .replace(/\\5c/g, "\\");
952    },
953
954    /** Function: getNodeFromJid
955     *  Get the node portion of a JID String.
956     *
957     *  Parameters:
958     *    (String) jid - A JID.
959     *
960     *  Returns:
961     *    A String containing the node.
962     */
963    getNodeFromJid: function (jid)
964    {
965        if (jid.indexOf("@") < 0) { return null; }
966        return jid.split("@")[0];
967    },
968
969    /** Function: getDomainFromJid
970     *  Get the domain portion of a JID String.
971     *
972     *  Parameters:
973     *    (String) jid - A JID.
974     *
975     *  Returns:
976     *    A String containing the domain.
977     */
978    getDomainFromJid: function (jid)
979    {
980        var bare = Strophe.getBareJidFromJid(jid);
981        if (bare.indexOf("@") < 0) {
982            return bare;
983        } else {
984            var parts = bare.split("@");
985            parts.splice(0, 1);
986            return parts.join('@');
987        }
988    },
989
990    /** Function: getResourceFromJid
991     *  Get the resource portion of a JID String.
992     *
993     *  Parameters:
994     *    (String) jid - A JID.
995     *
996     *  Returns:
997     *    A String containing the resource.
998     */
999    getResourceFromJid: function (jid)
1000    {
1001        var s = jid.split("/");
1002        if (s.length < 2) { return null; }
1003        s.splice(0, 1);
1004        return s.join('/');
1005    },
1006
1007    /** Function: getBareJidFromJid
1008     *  Get the bare JID from a JID String.
1009     *
1010     *  Parameters:
1011     *    (String) jid - A JID.
1012     *
1013     *  Returns:
1014     *    A String containing the bare JID.
1015     */
1016    getBareJidFromJid: function (jid)
1017    {
1018        return jid.split("/")[0];
1019    },
1020
1021    /** Function: log
1022     *  User overrideable logging function.
1023     *
1024     *  This function is called whenever the Strophe library calls any
1025     *  of the logging functions.  The default implementation of this
1026     *  function does nothing.  If client code wishes to handle the logging
1027     *  messages, it should override this with
1028     *  > Strophe.log = function (level, msg) {
1029     *  >   (user code here)
1030     *  > };
1031     *
1032     *  Please note that data sent and received over the wire is logged
1033     *  via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput().
1034     *
1035     *  The different levels and their meanings are
1036     *
1037     *    DEBUG - Messages useful for debugging purposes.
1038     *    INFO - Informational messages.  This is mostly information like
1039     *      'disconnect was called' or 'SASL auth succeeded'.
1040     *    WARN - Warnings about potential problems.  This is mostly used
1041     *      to report transient connection errors like request timeouts.
1042     *    ERROR - Some error occurred.
1043     *    FATAL - A non-recoverable fatal error occurred.
1044     *
1045     *  Parameters:
1046     *    (Integer) level - The log level of the log message.  This will
1047     *      be one of the values in Strophe.LogLevel.
1048     *    (String) msg - The log message.
1049     */
1050    log: function (level, msg)
1051    {
1052        return;
1053    },
1054
1055    /** Function: debug
1056     *  Log a message at the Strophe.LogLevel.DEBUG level.
1057     *
1058     *  Parameters:
1059     *    (String) msg - The log message.
1060     */
1061    debug: function(msg)
1062    {
1063        this.log(this.LogLevel.DEBUG, msg);
1064    },
1065
1066    /** Function: info
1067     *  Log a message at the Strophe.LogLevel.INFO level.
1068     *
1069     *  Parameters:
1070     *    (String) msg - The log message.
1071     */
1072    info: function (msg)
1073    {
1074        this.log(this.LogLevel.INFO, msg);
1075    },
1076
1077    /** Function: warn
1078     *  Log a message at the Strophe.LogLevel.WARN level.
1079     *
1080     *  Parameters:
1081     *    (String) msg - The log message.
1082     */
1083    warn: function (msg)
1084    {
1085        this.log(this.LogLevel.WARN, msg);
1086    },
1087
1088    /** Function: error
1089     *  Log a message at the Strophe.LogLevel.ERROR level.
1090     *
1091     *  Parameters:
1092     *    (String) msg - The log message.
1093     */
1094    error: function (msg)
1095    {
1096        this.log(this.LogLevel.ERROR, msg);
1097    },
1098
1099    /** Function: fatal
1100     *  Log a message at the Strophe.LogLevel.FATAL level.
1101     *
1102     *  Parameters:
1103     *    (String) msg - The log message.
1104     */
1105    fatal: function (msg)
1106    {
1107        this.log(this.LogLevel.FATAL, msg);
1108    },
1109
1110    /** Function: serialize
1111     *  Render a DOM element and all descendants to a String.
1112     *
1113     *  Parameters:
1114     *    (XMLElement) elem - A DOM element.
1115     *
1116     *  Returns:
1117     *    The serialized element tree as a String.
1118     */
1119    serialize: function (elem)
1120    {
1121        var result;
1122
1123        if (!elem) { return null; }
1124
1125        if (typeof(elem.tree) === "function") {
1126            elem = elem.tree();
1127        }
1128
1129        var nodeName = elem.nodeName;
1130        var i, child;
1131
1132        if (elem.getAttribute("_realname")) {
1133            nodeName = elem.getAttribute("_realname");
1134        }
1135
1136        result = "<" + nodeName;
1137        for (i = 0; i < elem.attributes.length; i++) {
1138               if(elem.attributes[i].nodeName != "_realname") {
1139                 result += " " + elem.attributes[i].nodeName.toLowerCase() +
1140                "='" + elem.attributes[i].value
1141                    .replace("&", "&amp;")
1142                       .replace("'", "&apos;")
1143                       .replace("<", "&lt;") + "'";
1144               }
1145        }
1146
1147        if (elem.childNodes.length > 0) {
1148            result += ">";
1149            for (i = 0; i < elem.childNodes.length; i++) {
1150                child = elem.childNodes[i];
1151                if (child.nodeType == Strophe.ElementType.NORMAL) {
1152                    // normal element, so recurse
1153                    result += Strophe.serialize(child);
1154                } else if (child.nodeType == Strophe.ElementType.TEXT) {
1155                    // text element
1156                    result += child.nodeValue;
1157                }
1158            }
1159            result += "</" + nodeName + ">";
1160        } else {
1161            result += "/>";
1162        }
1163
1164        return result;
1165    },
1166
1167    /** PrivateVariable: _requestId
1168     *  _Private_ variable that keeps track of the request ids for
1169     *  connections.
1170     */
1171    _requestId: 0,
1172
1173    /** PrivateVariable: Strophe.connectionPlugins
1174     *  _Private_ variable Used to store plugin names that need
1175     *  initialization on Strophe.Connection construction.
1176     */
1177    _connectionPlugins: {},
1178
1179    /** Function: addConnectionPlugin
1180     *  Extends the Strophe.Connection object with the given plugin.
1181     *
1182     *  Paramaters:
1183     *    (String) name - The name of the extension.
1184     *    (Object) ptype - The plugin's prototype.
1185     */
1186    addConnectionPlugin: function (name, ptype)
1187    {
1188        Strophe._connectionPlugins[name] = ptype;
1189    }
1190};
1191
1192/** Class: Strophe.Builder
1193 *  XML DOM builder.
1194 *
1195 *  This object provides an interface similar to JQuery but for building
1196 *  DOM element easily and rapidly.  All the functions except for toString()
1197 *  and tree() return the object, so calls can be chained.  Here's an
1198 *  example using the $iq() builder helper.
1199 *  > $iq({to: 'you': from: 'me': type: 'get', id: '1'})
1200 *  >     .c('query', {xmlns: 'strophe:example'})
1201 *  >     .c('example')
1202 *  >     .toString()
1203 *  The above generates this XML fragment
1204 *  > <iq to='you' from='me' type='get' id='1'>
1205 *  >   <query xmlns='strophe:example'>
1206 *  >     <example/>
1207 *  >   </query>
1208 *  > </iq>
1209 *  The corresponding DOM manipulations to get a similar fragment would be
1210 *  a lot more tedious and probably involve several helper variables.
1211 *
1212 *  Since adding children makes new operations operate on the child, up()
1213 *  is provided to traverse up the tree.  To add two children, do
1214 *  > builder.c('child1', ...).up().c('child2', ...)
1215 *  The next operation on the Builder will be relative to the second child.
1216 */
1217
1218/** Constructor: Strophe.Builder
1219 *  Create a Strophe.Builder object.
1220 *
1221 *  The attributes should be passed in object notation.  For example
1222 *  > var b = new Builder('message', {to: 'you', from: 'me'});
1223 *  or
1224 *  > var b = new Builder('messsage', {'xml:lang': 'en'});
1225 *
1226 *  Parameters:
1227 *    (String) name - The name of the root element.
1228 *    (Object) attrs - The attributes for the root element in object notation.
1229 *
1230 *  Returns:
1231 *    A new Strophe.Builder.
1232 */
1233Strophe.Builder = function (name, attrs)
1234{
1235    // Set correct namespace for jabber:client elements
1236    if (name == "presence" || name == "message" || name == "iq") {
1237        if (attrs && !attrs.xmlns) {
1238            attrs.xmlns = Strophe.NS.CLIENT;
1239        } else if (!attrs) {
1240            attrs = {xmlns: Strophe.NS.CLIENT};
1241        }
1242    }
1243
1244    // Holds the tree being built.
1245    this.nodeTree = Strophe.xmlElement(name, attrs);
1246
1247    // Points to the current operation node.
1248    this.node = this.nodeTree;
1249};
1250
1251Strophe.Builder.prototype = {
1252    /** Function: tree
1253     *  Return the DOM tree.
1254     *
1255     *  This function returns the current DOM tree as an element object.  This
1256     *  is suitable for passing to functions like Strophe.Connection.send().
1257     *
1258     *  Returns:
1259     *    The DOM tree as a element object.
1260     */
1261    tree: function ()
1262    {
1263        return this.nodeTree;
1264    },
1265
1266    /** Function: toString
1267     *  Serialize the DOM tree to a String.
1268     *
1269     *  This function returns a string serialization of the current DOM
1270     *  tree.  It is often used internally to pass data to a
1271     *  Strophe.Request object.
1272     *
1273     *  Returns:
1274     *    The serialized DOM tree in a String.
1275     */
1276    toString: function ()
1277    {
1278        return Strophe.serialize(this.nodeTree);
1279    },
1280
1281    /** Function: up
1282     *  Make the current parent element the new current element.
1283     *
1284     *  This function is often used after c() to traverse back up the tree.
1285     *  For example, to add two children to the same element
1286     *  > builder.c('child1', {}).up().c('child2', {});
1287     *
1288     *  Returns:
1289     *    The Stophe.Builder object.
1290     */
1291    up: function ()
1292    {
1293        this.node = this.node.parentNode;
1294        return this;
1295    },
1296
1297    /** Function: attrs
1298     *  Add or modify attributes of the current element.
1299     *
1300     *  The attributes should be passed in object notation.  This function
1301     *  does not move the current element pointer.
1302     *
1303     *  Parameters:
1304     *    (Object) moreattrs - The attributes to add/modify in object notation.
1305     *
1306     *  Returns:
1307     *    The Strophe.Builder object.
1308     */
1309    attrs: function (moreattrs)
1310    {
1311        for (var k in moreattrs) {
1312            if (moreattrs.hasOwnProperty(k)) {
1313                this.node.setAttribute(k, moreattrs[k]);
1314            }
1315        }
1316        return this;
1317    },
1318
1319    /** Function: c
1320     *  Add a child to the current element and make it the new current
1321     *  element.
1322     *
1323     *  This function moves the current element pointer to the child.  If you
1324     *  need to add another child, it is necessary to use up() to go back
1325     *  to the parent in the tree.
1326     *
1327     *  Parameters:
1328     *    (String) name - The name of the child.
1329     *    (Object) attrs - The attributes of the child in object notation.
1330     *
1331     *  Returns:
1332     *    The Strophe.Builder object.
1333     */
1334    c: function (name, attrs)
1335    {
1336        var child = Strophe.xmlElement(name, attrs);
1337        this.node.appendChild(child);
1338        this.node = child;
1339        return this;
1340    },
1341
1342    /** Function: cnode
1343     *  Add a child to the current element and make it the new current
1344     *  element.
1345     *
1346     *  This function is the same as c() except that instead of using a
1347     *  name and an attributes object to create the child it uses an
1348     *  existing DOM element object.
1349     *
1350     *  Parameters:
1351     *    (XMLElement) elem - A DOM element.
1352     *
1353     *  Returns:
1354     *    The Strophe.Builder object.
1355     */
1356    cnode: function (elem)
1357    {
1358        this.node.appendChild(elem);
1359        this.node = elem;
1360        return this;
1361    },
1362
1363    /** Function: t
1364     *  Add a child text element.
1365     *
1366     *  This *does not* make the child the new current element since there
1367     *  are no children of text elements.
1368     *
1369     *  Parameters:
1370     *    (String) text - The text data to append to the current element.
1371     *
1372     *  Returns:
1373     *    The Strophe.Builder object.
1374     */
1375    t: function (text)
1376    {
1377        var child = Strophe.xmlTextNode(text);
1378        this.node.appendChild(child);
1379        return this;
1380    }
1381};
1382
1383
1384/** PrivateClass: Strophe.Handler
1385 *  _Private_ helper class for managing stanza handlers.
1386 *
1387 *  A Strophe.Handler encapsulates a user provided callback function to be
1388 *  executed when matching stanzas are received by the connection.
1389 *  Handlers can be either one-off or persistant depending on their
1390 *  return value. Returning true will cause a Handler to remain active, and
1391 *  returning false will remove the Handler.
1392 *
1393 *  Users will not use Strophe.Handler objects directly, but instead they
1394 *  will use Strophe.Connection.addHandler() and
1395 *  Strophe.Connection.deleteHandler().
1396 */
1397
1398/** PrivateConstructor: Strophe.Handler
1399 *  Create and initialize a new Strophe.Handler.
1400 *
1401 *  Parameters:
1402 *    (Function) handler - A function to be executed when the handler is run.
1403 *    (String) ns - The namespace to match.
1404 *    (String) name - The element name to match.
1405 *    (String) type - The element type to match.
1406 *    (String) id - The element id attribute to match.
1407 *    (String) from - The element from attribute to match.
1408 *    (Object) options - Handler options
1409 *
1410 *  Returns:
1411 *    A new Strophe.Handler object.
1412 */
1413Strophe.Handler = function (handler, ns, name, type, id, from, options)
1414{
1415    this.handler = handler;
1416    this.ns = ns;
1417    this.name = name;
1418    this.type = type;
1419    this.id = id;
1420    this.options = options || {matchbare: false};
1421   
1422    // default matchBare to false if undefined
1423    if (!this.options.matchBare) {
1424        this.options.matchBare = false;
1425    }
1426
1427    if (this.options.matchBare) {
1428        this.from = Strophe.getBareJidFromJid(from);
1429    } else {
1430        this.from = from;
1431    }
1432
1433    // whether the handler is a user handler or a system handler
1434    this.user = true;
1435};
1436
1437Strophe.Handler.prototype = {
1438    /** PrivateFunction: isMatch
1439     *  Tests if a stanza matches the Strophe.Handler.
1440     *
1441     *  Parameters:
1442     *    (XMLElement) elem - The XML element to test.
1443     *
1444     *  Returns:
1445     *    true if the stanza matches and false otherwise.
1446     */
1447    isMatch: function (elem)
1448    {
1449        var nsMatch;
1450        var from = null;
1451       
1452        if (this.options.matchBare) {
1453            from = Strophe.getBareJidFromJid(elem.getAttribute('from'));
1454        } else {
1455            from = elem.getAttribute('from');
1456        }
1457
1458        nsMatch = false;
1459        if (!this.ns) {
1460            nsMatch = true;
1461        } else {
1462            var self = this;
1463            Strophe.forEachChild(elem, null, function (elem) {
1464                if (elem.getAttribute("xmlns") == self.ns) {
1465                    nsMatch = true;
1466                }
1467            });
1468
1469            nsMatch = nsMatch || elem.getAttribute("xmlns") == this.ns;
1470        }
1471
1472        if (nsMatch &&
1473            (!this.name || Strophe.isTagEqual(elem, this.name)) &&
1474            (!this.type || elem.getAttribute("type") === this.type) &&
1475            (!this.id || elem.getAttribute("id") === this.id) &&
1476            (!this.from || from === this.from)) {
1477                return true;
1478        }
1479
1480        return false;
1481    },
1482
1483    /** PrivateFunction: run
1484     *  Run the callback on a matching stanza.
1485     *
1486     *  Parameters:
1487     *    (XMLElement) elem - The DOM element that triggered the
1488     *      Strophe.Handler.
1489     *
1490     *  Returns:
1491     *    A boolean indicating if the handler should remain active.
1492     */
1493    run: function (elem)
1494    {
1495        var result = null;
1496        try {
1497            result = this.handler(elem);
1498        } catch (e) {
1499            if (e.sourceURL) {
1500                Strophe.fatal("error: " + this.handler +
1501                              " " + e.sourceURL + ":" +
1502                              e.line + " - " + e.name + ": " + e.message);
1503            } else if (e.fileName) {
1504                if (typeof(console) != "undefined") {
1505                    console.trace();
1506                    console.error(this.handler, " - error - ", e, e.message);
1507                }
1508                Strophe.fatal("error: " + this.handler + " " +
1509                              e.fileName + ":" + e.lineNumber + " - " +
1510                              e.name + ": " + e.message);
1511            } else {
1512                Strophe.fatal("error: " + this.handler);
1513            }
1514
1515            throw e;
1516        }
1517
1518        return result;
1519    },
1520
1521    /** PrivateFunction: toString
1522     *  Get a String representation of the Strophe.Handler object.
1523     *
1524     *  Returns:
1525     *    A String.
1526     */
1527    toString: function ()
1528    {
1529        return "{Handler: " + this.handler + "(" + this.name + "," +
1530            this.id + "," + this.ns + ")}";
1531    }
1532};
1533
1534/** PrivateClass: Strophe.TimedHandler
1535 *  _Private_ helper class for managing timed handlers.
1536 *
1537 *  A Strophe.TimedHandler encapsulates a user provided callback that
1538 *  should be called after a certain period of time or at regular
1539 *  intervals.  The return value of the callback determines whether the
1540 *  Strophe.TimedHandler will continue to fire.
1541 *
1542 *  Users will not use Strophe.TimedHandler objects directly, but instead
1543 *  they will use Strophe.Connection.addTimedHandler() and
1544 *  Strophe.Connection.deleteTimedHandler().
1545 */
1546
1547/** PrivateConstructor: Strophe.TimedHandler
1548 *  Create and initialize a new Strophe.TimedHandler object.
1549 *
1550 *  Parameters:
1551 *    (Integer) period - The number of milliseconds to wait before the
1552 *      handler is called.
1553 *    (Function) handler - The callback to run when the handler fires.  This
1554 *      function should take no arguments.
1555 *
1556 *  Returns:
1557 *    A new Strophe.TimedHandler object.
1558 */
1559Strophe.TimedHandler = function (period, handler)
1560{
1561    this.period = period;
1562    this.handler = handler;
1563
1564    this.lastCalled = new Date().getTime();
1565    this.user = true;
1566};
1567
1568Strophe.TimedHandler.prototype = {
1569    /** PrivateFunction: run
1570     *  Run the callback for the Strophe.TimedHandler.
1571     *
1572     *  Returns:
1573     *    true if the Strophe.TimedHandler should be called again, and false
1574     *      otherwise.
1575     */
1576    run: function ()
1577    {
1578        this.lastCalled = new Date().getTime();
1579        return this.handler();
1580    },
1581
1582    /** PrivateFunction: reset
1583     *  Reset the last called time for the Strophe.TimedHandler.
1584     */
1585    reset: function ()
1586    {
1587        this.lastCalled = new Date().getTime();
1588    },
1589
1590    /** PrivateFunction: toString
1591     *  Get a string representation of the Strophe.TimedHandler object.
1592     *
1593     *  Returns:
1594     *    The string representation.
1595     */
1596    toString: function ()
1597    {
1598        return "{TimedHandler: " + this.handler + "(" + this.period +")}";
1599    }
1600};
1601
1602/** PrivateClass: Strophe.Request
1603 *  _Private_ helper class that provides a cross implementation abstraction
1604 *  for a BOSH related XMLHttpRequest.
1605 *
1606 *  The Strophe.Request class is used internally to encapsulate BOSH request
1607 *  information.  It is not meant to be used from user's code.
1608 */
1609
1610/** PrivateConstructor: Strophe.Request
1611 *  Create and initialize a new Strophe.Request object.
1612 *
1613 *  Parameters:
1614 *    (XMLElement) elem - The XML data to be sent in the request.
1615 *    (Function) func - The function that will be called when the
1616 *      XMLHttpRequest readyState changes.
1617 *    (Integer) rid - The BOSH rid attribute associated with this request.
1618 *    (Integer) sends - The number of times this same request has been
1619 *      sent.
1620 */
1621Strophe.Request = function (elem, func, rid, sends)
1622{
1623    this.id = ++Strophe._requestId;
1624    this.xmlData = elem;
1625    this.data = Strophe.serialize(elem);
1626    // save original function in case we need to make a new request
1627    // from this one.
1628    this.origFunc = func;
1629    this.func = func;
1630    this.rid = rid;
1631    this.date = NaN;
1632    this.sends = sends || 0;
1633    this.abort = false;
1634    this.dead = null;
1635    this.age = function () {
1636        if (!this.date) { return 0; }
1637        var now = new Date();
1638        return (now - this.date) / 1000;
1639    };
1640    this.timeDead = function () {
1641        if (!this.dead) { return 0; }
1642        var now = new Date();
1643        return (now - this.dead) / 1000;
1644    };
1645    this.xhr = this._newXHR();
1646};
1647
1648Strophe.Request.prototype = {
1649    /** PrivateFunction: getResponse
1650     *  Get a response from the underlying XMLHttpRequest.
1651     *
1652     *  This function attempts to get a response from the request and checks
1653     *  for errors.
1654     *
1655     *  Throws:
1656     *    "parsererror" - A parser error occured.
1657     *
1658     *  Returns:
1659     *    The DOM element tree of the response.
1660     */
1661    getResponse: function ()
1662    {
1663        var node = null;
1664        if (this.xhr.responseXML && this.xhr.responseXML.documentElement) {
1665            node = this.xhr.responseXML.documentElement;
1666            if (node.tagName == "parsererror") {
1667                Strophe.error("invalid response received");
1668                Strophe.error("responseText: " + this.xhr.responseText);
1669                Strophe.error("responseXML: " +
1670                              Strophe.serialize(this.xhr.responseXML));
1671                throw "parsererror";
1672            }
1673        } else if (this.xhr.responseText) {
1674            Strophe.error("invalid response received");
1675            Strophe.error("responseText: " + this.xhr.responseText);
1676            Strophe.error("responseXML: " +
1677                          Strophe.serialize(this.xhr.responseXML));
1678        }
1679
1680        return node;
1681    },
1682
1683    /** PrivateFunction: _newXHR
1684     *  _Private_ helper function to create XMLHttpRequests.
1685     *
1686     *  This function creates XMLHttpRequests across all implementations.
1687     *
1688     *  Returns:
1689     *    A new XMLHttpRequest.
1690     */
1691    _newXHR: function ()
1692    {
1693        var xhr = null;
1694        if (window.XMLHttpRequest) {
1695            xhr = new XMLHttpRequest();
1696            if (xhr.overrideMimeType) {
1697                xhr.overrideMimeType("text/xml");
1698            }
1699        } else if (window.ActiveXObject) {
1700            xhr = new ActiveXObject("Microsoft.XMLHTTP");
1701        }
1702
1703        xhr.onreadystatechange = this.func.prependArg(this);
1704
1705        return xhr;
1706    }
1707};
1708
1709/** Class: Strophe.Connection
1710 *  XMPP Connection manager.
1711 *
1712 *  Thie class is the main part of Strophe.  It manages a BOSH connection
1713 *  to an XMPP server and dispatches events to the user callbacks as
1714 *  data arrives.  It supports SASL PLAIN, SASL DIGEST-MD5, and legacy
1715 *  authentication.
1716 *
1717 *  After creating a Strophe.Connection object, the user will typically
1718 *  call connect() with a user supplied callback to handle connection level
1719 *  events like authentication failure, disconnection, or connection
1720 *  complete.
1721 *
1722 *  The user will also have several event handlers defined by using
1723 *  addHandler() and addTimedHandler().  These will allow the user code to
1724 *  respond to interesting stanzas or do something periodically with the
1725 *  connection.  These handlers will be active once authentication is
1726 *  finished.
1727 *
1728 *  To send data to the connection, use send().
1729 */
1730
1731/** Constructor: Strophe.Connection
1732 *  Create and initialize a Strophe.Connection object.
1733 *
1734 *  Parameters:
1735 *    (String) service - The BOSH service URL.
1736 *
1737 *  Returns:
1738 *    A new Strophe.Connection object.
1739 */
1740Strophe.Connection = function (service)
1741{
1742    /* The path to the httpbind service. */
1743    this.service = service;
1744    /* The connected JID. */
1745    this.jid = "";
1746    /* request id for body tags */
1747    this.rid = Math.floor(Math.random() * 4294967295);
1748    /* The current session ID. */
1749    this.sid = null;
1750    this.streamId = null;
1751
1752    // SASL
1753    this.do_session = false;
1754    this.do_bind = false;
1755
1756    // handler lists
1757    this.timedHandlers = [];
1758    this.handlers = [];
1759    this.removeTimeds = [];
1760    this.removeHandlers = [];
1761    this.addTimeds = [];
1762    this.addHandlers = [];
1763
1764    this._idleTimeout = null;
1765    this._disconnectTimeout = null;
1766
1767    this.authenticated = false;
1768    this.disconnecting = false;
1769    this.connected = false;
1770
1771    this.errors = 0;
1772
1773    this.paused = false;
1774
1775    // default BOSH values
1776    this.hold = 1;
1777    this.wait = 60;
1778    this.window = 5;
1779
1780    this._data = [];
1781    this._requests = [];
1782    this._uniqueId = Math.round(Math.random() * 10000);
1783
1784    this._sasl_success_handler = null;
1785    this._sasl_failure_handler = null;
1786    this._sasl_challenge_handler = null;
1787
1788    // setup onIdle callback every 1/10th of a second
1789    this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
1790
1791    // initialize plugins
1792    for (var k in Strophe._connectionPlugins) {
1793        if (Strophe._connectionPlugins.hasOwnProperty(k)) {
1794            var ptype = Strophe._connectionPlugins[k];
1795            // jslint complaints about the below line, but this is fine
1796            var F = function () {};
1797            F.prototype = ptype;
1798            this[k] = new F();
1799            this[k].init(this);
1800        }
1801    }
1802};
1803
1804Strophe.Connection.prototype = {
1805    /** Function: reset
1806     *  Reset the connection.
1807     *
1808     *  This function should be called after a connection is disconnected
1809     *  before that connection is reused.
1810     */
1811    reset: function ()
1812    {
1813        this.rid = Math.floor(Math.random() * 4294967295);
1814
1815        this.sid = null;
1816        this.streamId = null;
1817
1818        // SASL
1819        this.do_session = false;
1820        this.do_bind = false;
1821
1822        // handler lists
1823        this.timedHandlers = [];
1824        this.handlers = [];
1825        this.removeTimeds = [];
1826        this.removeHandlers = [];
1827        this.addTimeds = [];
1828        this.addHandlers = [];
1829
1830        this.authenticated = false;
1831        this.disconnecting = false;
1832        this.connected = false;
1833
1834        this.errors = 0;
1835
1836        this._requests = [];
1837        this._uniqueId = Math.round(Math.random()*10000);
1838    },
1839
1840    /** Function: pause
1841     *  Pause the request manager.
1842     *
1843     *  This will prevent Strophe from sending any more requests to the
1844     *  server.  This is very useful for temporarily pausing while a lot
1845     *  of send() calls are happening quickly.  This causes Strophe to
1846     *  send the data in a single request, saving many request trips.
1847     */
1848    pause: function ()
1849    {
1850        this.paused = true;
1851    },
1852
1853    /** Function: resume
1854     *  Resume the request manager.
1855     *
1856     *  This resumes after pause() has been called.
1857     */
1858    resume: function ()
1859    {
1860        this.paused = false;
1861    },
1862
1863    /** Function: getUniqueId
1864     *  Generate a unique ID for use in <iq/> elements.
1865     *
1866     *  All <iq/> stanzas are required to have unique id attributes.  This
1867     *  function makes creating these easy.  Each connection instance has
1868     *  a counter which starts from zero, and the value of this counter
1869     *  plus a colon followed by the suffix becomes the unique id. If no
1870     *  suffix is supplied, the counter is used as the unique id.
1871     *
1872     *  Suffixes are used to make debugging easier when reading the stream
1873     *  data, and their use is recommended.  The counter resets to 0 for
1874     *  every new connection for the same reason.  For connections to the
1875     *  same server that authenticate the same way, all the ids should be
1876     *  the same, which makes it easy to see changes.  This is useful for
1877     *  automated testing as well.
1878     *
1879     *  Parameters:
1880     *    (String) suffix - A optional suffix to append to the id.
1881     *
1882     *  Returns:
1883     *    A unique string to be used for the id attribute.
1884     */
1885    getUniqueId: function (suffix)
1886    {
1887        if (typeof(suffix) == "string" || typeof(suffix) == "number") {
1888            return ++this._uniqueId + ":" + suffix;
1889        } else {
1890            return ++this._uniqueId + "";
1891        }
1892    },
1893
1894    /** Function: connect
1895     *  Starts the connection process.
1896     *
1897     *  As the connection process proceeds, the user supplied callback will
1898     *  be triggered multiple times with status updates.  The callback
1899     *  should take two arguments - the status code and the error condition.
1900     *
1901     *  The status code will be one of the values in the Strophe.Status
1902     *  constants.  The error condition will be one of the conditions
1903     *  defined in RFC 3920 or the condition 'strophe-parsererror'.
1904     *
1905     *  Please see XEP 124 for a more detailed explanation of the optional
1906     *  parameters below.
1907     *
1908     *  Parameters:
1909     *    (String) jid - The user's JID.  This may be a bare JID,
1910     *      or a full JID.  If a node is not supplied, SASL ANONYMOUS
1911     *      authentication will be attempted.
1912     *    (String) pass - The user's password.
1913     *    (Function) callback The connect callback function.
1914     *    (Integer) wait - The optional HTTPBIND wait value.  This is the
1915     *      time the server will wait before returning an empty result for
1916     *      a request.  The default setting of 60 seconds is recommended.
1917     *      Other settings will require tweaks to the Strophe.TIMEOUT value.
1918     *    (Integer) hold - The optional HTTPBIND hold value.  This is the
1919     *      number of connections the server will hold at one time.  This
1920     *      should almost always be set to 1 (the default).
1921     */
1922    connect: function (jid, pass, callback, wait, hold)
1923    {
1924        this.jid = jid;
1925        this.pass = pass;
1926        this.connect_callback = callback;
1927        this.disconnecting = false;
1928        this.connected = false;
1929        this.authenticated = false;
1930        this.errors = 0;
1931
1932        this.wait = wait || this.wait;
1933        this.hold = hold || this.hold;
1934
1935        // parse jid for domain and resource
1936        this.domain = Strophe.getDomainFromJid(this.jid);
1937
1938        // build the body tag
1939        var body = this._buildBody().attrs({
1940            to: this.domain,
1941            "xml:lang": "en",
1942            wait: this.wait,
1943            hold: this.hold,
1944            content: "text/xml; charset=utf-8",
1945            ver: "1.6",
1946            "xmpp:version": "1.0",
1947            "xmlns:xmpp": Strophe.NS.BOSH
1948        });
1949
1950        this._changeConnectStatus(Strophe.Status.CONNECTING, null);
1951
1952        this._requests.push(
1953            new Strophe.Request(body.tree(),
1954                                this._onRequestStateChange.bind(this)
1955                                    .prependArg(this._connect_cb.bind(this)),
1956                                body.tree().getAttribute("rid")));
1957        this._throttledRequestHandler();
1958    },
1959
1960    /** Function: attach
1961     *  Attach to an already created and authenticated BOSH session.
1962     *
1963     *  This function is provided to allow Strophe to attach to BOSH
1964     *  sessions which have been created externally, perhaps by a Web
1965     *  application.  This is often used to support auto-login type features
1966     *  without putting user credentials into the page.
1967     *
1968     *  Parameters:
1969     *    (String) jid - The full JID that is bound by the session.
1970     *    (String) sid - The SID of the BOSH session.
1971     *    (String) rid - The current RID of the BOSH session.  This RID
1972     *      will be used by the next request.
1973     *    (Function) callback The connect callback function.
1974     *    (Integer) wait - The optional HTTPBIND wait value.  This is the
1975     *      time the server will wait before returning an empty result for
1976     *      a request.  The default setting of 60 seconds is recommended.
1977     *      Other settings will require tweaks to the Strophe.TIMEOUT value.
1978     *    (Integer) hold - The optional HTTPBIND hold value.  This is the
1979     *      number of connections the server will hold at one time.  This
1980     *      should almost always be set to 1 (the default).
1981     *    (Integer) wind - The optional HTTBIND window value.  This is the
1982     *      allowed range of request ids that are valid.  The default is 5.
1983     */
1984    attach: function (jid, sid, rid, callback, wait, hold, wind)
1985    {
1986        this.jid = jid;
1987        this.sid = sid;
1988        this.rid = rid;
1989        this.connect_callback = callback;
1990
1991        this.domain = Strophe.getDomainFromJid(this.jid);
1992
1993        this.authenticated = true;
1994        this.connected = true;
1995
1996        this.wait = wait || this.wait;
1997        this.hold = hold || this.hold;
1998        this.window = wind || this.window;
1999
2000        this._changeConnectStatus(Strophe.Status.ATTACHED, null);
2001    },
2002
2003    /** Function: xmlInput
2004     *  User overrideable function that receives XML data coming into the
2005     *  connection.
2006     *
2007     *  The default function does nothing.  User code can override this with
2008     *  > Strophe.Connection.xmlInput = function (elem) {
2009     *  >   (user code)
2010     *  > };
2011     *
2012     *  Parameters:
2013     *    (XMLElement) elem - The XML data received by the connection.
2014     */
2015    xmlInput: function (elem)
2016    {
2017        return;
2018    },
2019
2020    /** Function: xmlOutput
2021     *  User overrideable function that receives XML data sent to the
2022     *  connection.
2023     *
2024     *  The default function does nothing.  User code can override this with
2025     *  > Strophe.Connection.xmlOutput = function (elem) {
2026     *  >   (user code)
2027     *  > };
2028     *
2029     *  Parameters:
2030     *    (XMLElement) elem - The XMLdata sent by the connection.
2031     */
2032    xmlOutput: function (elem)
2033    {
2034        return;
2035    },
2036
2037    /** Function: rawInput
2038     *  User overrideable function that receives raw data coming into the
2039     *  connection.
2040     *
2041     *  The default function does nothing.  User code can override this with
2042     *  > Strophe.Connection.rawInput = function (data) {
2043     *  >   (user code)
2044     *  > };
2045     *
2046     *  Parameters:
2047     *    (String) data - The data received by the connection.
2048     */
2049    rawInput: function (data)
2050    {
2051        return;
2052    },
2053
2054    /** Function: rawOutput
2055     *  User overrideable function that receives raw data sent to the
2056     *  connection.
2057     *
2058     *  The default function does nothing.  User code can override this with
2059     *  > Strophe.Connection.rawOutput = function (data) {
2060     *  >   (user code)
2061     *  > };
2062     *
2063     *  Parameters:
2064     *    (String) data - The data sent by the connection.
2065     */
2066    rawOutput: function (data)
2067    {
2068        return;
2069    },
2070
2071    /** Function: send
2072     *  Send a stanza.
2073     *
2074     *  This function is called to push data onto the send queue to
2075     *  go out over the wire.  Whenever a request is sent to the BOSH
2076     *  server, all pending data is sent and the queue is flushed.
2077     *
2078     *  Parameters:
2079     *    (XMLElement |
2080     *     [XMLElement] |
2081     *     Strophe.Builder) elem - The stanza to send.
2082     */
2083    send: function (elem)
2084    {
2085        if (elem === null) { return ; }
2086        if (typeof(elem.sort) === "function") {
2087            for (var i = 0; i < elem.length; i++) {
2088                this._queueData(elem[i]);
2089            }
2090        } else if (typeof(elem.tree) === "function") {
2091            this._queueData(elem.tree());
2092        } else {
2093            this._queueData(elem);
2094        }
2095
2096        this._throttledRequestHandler();
2097        clearTimeout(this._idleTimeout);
2098        this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
2099    },
2100
2101    /** Function: flush
2102     *  Immediately send any pending outgoing data.
2103     * 
2104     *  Normally send() queues outgoing data until the next idle period
2105     *  (100ms), which optimizes network use in the common cases when
2106     *  several send()s are called in succession. flush() can be used to
2107     *  immediately send all pending data.
2108     */
2109    flush: function ()
2110    {
2111        // cancel the pending idle period and run the idle function
2112        // immediately
2113        clearTimeout(this._idleTimeout);
2114        this._onIdle();
2115    },
2116
2117    /** Function: sendIQ
2118     *  Helper function to send IQ stanzas.
2119     *
2120     *  Parameters:
2121     *    (XMLElement) elem - The stanza to send.
2122     *    (Function) callback - The callback function for a successful request.
2123     *    (Function) errback - The callback function for a failed or timed
2124     *      out request.  On timeout, the stanza will be null.
2125     *    (Integer) timeout - The time specified in milliseconds for a
2126     *      timeout to occur.
2127     *
2128     *  Returns:
2129     *    The id used to send the IQ.
2130    */
2131    sendIQ: function(elem, callback, errback, timeout) {
2132        var timeoutHandler = null;
2133        var that = this;
2134
2135        if (typeof(elem.tree) === "function") {
2136            elem = elem.tree();
2137        }
2138        var id = elem.getAttribute('id');
2139
2140        // inject id if not found
2141        if (!id) {
2142            id = this.getUniqueId("sendIQ");
2143            elem.setAttribute("id", id);
2144        }
2145
2146        var handler = this.addHandler(function (stanza) {
2147            // remove timeout handler if there is one
2148            if (timeoutHandler) {
2149                that.deleteTimedHandler(timeoutHandler);
2150            }
2151
2152            var iqtype = stanza.getAttribute('type');
2153            if (iqtype === 'result') {
2154                if (callback) {
2155                    callback(stanza);
2156                }
2157            } else if (iqtype === 'error') {
2158                if (errback) {
2159                    errback(stanza);
2160                }
2161            } else {
2162                throw {
2163                    name: "StropheError",
2164                    message: "Got bad IQ type of " + iqtype
2165                };
2166            }
2167        }, null, 'iq', null, id);
2168
2169        // if timeout specified, setup timeout handler.
2170        if (timeout) {
2171            timeoutHandler = this.addTimedHandler(timeout, function () {
2172                // get rid of normal handler
2173                that.deleteHandler(handler);
2174
2175                // call errback on timeout with null stanza
2176                if (errback) {
2177                    errback(null);
2178                }
2179                return false;
2180            });
2181        }
2182
2183        this.send(elem);
2184
2185        return id;
2186    },
2187
2188    /** PrivateFunction: _queueData
2189     *  Queue outgoing data for later sending.  Also ensures that the data
2190     *  is a DOMElement.
2191     */
2192    _queueData: function (element) {
2193        if (element === null ||
2194            !element.tagName ||
2195            !element.childNodes) {
2196            throw {
2197                name: "StropheError",
2198                message: "Cannot queue non-DOMElement."
2199            };
2200        }
2201       
2202        this._data.push(element);
2203    },
2204
2205    /** PrivateFunction: _sendRestart
2206     *  Send an xmpp:restart stanza.
2207     */
2208    _sendRestart: function ()
2209    {
2210        this._data.push("restart");
2211
2212        this._throttledRequestHandler();
2213        clearTimeout(this._idleTimeout);
2214        this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
2215    },
2216
2217    /** Function: addTimedHandler
2218     *  Add a timed handler to the connection.
2219     *
2220     *  This function adds a timed handler.  The provided handler will
2221     *  be called every period milliseconds until it returns false,
2222     *  the connection is terminated, or the handler is removed.  Handlers
2223     *  that wish to continue being invoked should return true.
2224     *
2225     *  Because of method binding it is necessary to save the result of
2226     *  this function if you wish to remove a handler with
2227     *  deleteTimedHandler().
2228     *
2229     *  Note that user handlers are not active until authentication is
2230     *  successful.
2231     *
2232     *  Parameters:
2233     *    (Integer) period - The period of the handler.
2234     *    (Function) handler - The callback function.
2235     *
2236     *  Returns:
2237     *    A reference to the handler that can be used to remove it.
2238     */
2239    addTimedHandler: function (period, handler)
2240    {
2241        var thand = new Strophe.TimedHandler(period, handler);
2242        this.addTimeds.push(thand);
2243        return thand;
2244    },
2245
2246    /** Function: deleteTimedHandler
2247     *  Delete a timed handler for a connection.
2248     *
2249     *  This function removes a timed handler from the connection.  The
2250     *  handRef parameter is *not* the function passed to addTimedHandler(),
2251     *  but is the reference returned from addTimedHandler().
2252     *
2253     *  Parameters:
2254     *    (Strophe.TimedHandler) handRef - The handler reference.
2255     */
2256    deleteTimedHandler: function (handRef)
2257    {
2258        // this must be done in the Idle loop so that we don't change
2259        // the handlers during iteration
2260        this.removeTimeds.push(handRef);
2261    },
2262
2263    /** Function: addHandler
2264     *  Add a stanza handler for the connection.
2265     *
2266     *  This function adds a stanza handler to the connection.  The
2267     *  handler callback will be called for any stanza that matches
2268     *  the parameters.  Note that if multiple parameters are supplied,
2269     *  they must all match for the handler to be invoked.
2270     *
2271     *  The handler will receive the stanza that triggered it as its argument.
2272     *  The handler should return true if it is to be invoked again;
2273     *  returning false will remove the handler after it returns.
2274     *
2275     *  As a convenience, the ns parameters applies to the top level element
2276     *  and also any of its immediate children.  This is primarily to make
2277     *  matching /iq/query elements easy.
2278     *
2279     *  The options argument contains handler matching flags that affect how
2280     *  matches are determined. Currently the only flag is matchBare (a
2281     *  boolean). When matchBare is true, the from parameter and the from
2282     *  attribute on the stanza will be matched as bare JIDs instead of
2283     *  full JIDs. To use this, pass {matchBare: true} as the value of
2284     *  options. The default value for matchBare is false.
2285     *
2286     *  The return value should be saved if you wish to remove the handler
2287     *  with deleteHandler().
2288     *
2289     *  Parameters:
2290     *    (Function) handler - The user callback.
2291     *    (String) ns - The namespace to match.
2292     *    (String) name - The stanza name to match.
2293     *    (String) type - The stanza type attribute to match.
2294     *    (String) id - The stanza id attribute to match.
2295     *    (String) from - The stanza from attribute to match.
2296     *    (String) options - The handler options
2297     *
2298     *  Returns:
2299     *    A reference to the handler that can be used to remove it.
2300     */
2301    addHandler: function (handler, ns, name, type, id, from, options)
2302    {
2303        var hand = new Strophe.Handler(handler, ns, name, type, id, from, options);
2304        this.addHandlers.push(hand);
2305        return hand;
2306    },
2307
2308    /** Function: deleteHandler
2309     *  Delete a stanza handler for a connection.
2310     *
2311     *  This function removes a stanza handler from the connection.  The
2312     *  handRef parameter is *not* the function passed to addHandler(),
2313     *  but is the reference returned from addHandler().
2314     *
2315     *  Parameters:
2316     *    (Strophe.Handler) handRef - The handler reference.
2317     */
2318    deleteHandler: function (handRef)
2319    {
2320        // this must be done in the Idle loop so that we don't change
2321        // the handlers during iteration
2322        this.removeHandlers.push(handRef);
2323    },
2324
2325    /** Function: disconnect
2326     *  Start the graceful disconnection process.
2327     *
2328     *  This function starts the disconnection process.  This process starts
2329     *  by sending unavailable presence and sending BOSH body of type
2330     *  terminate.  A timeout handler makes sure that disconnection happens
2331     *  even if the BOSH server does not respond.
2332     *
2333     *  The user supplied connection callback will be notified of the
2334     *  progress as this process happens.
2335     *
2336     *  Parameters:
2337     *    (String) reason - The reason the disconnect is occuring.
2338     */
2339    disconnect: function (reason)
2340    {
2341        this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);
2342
2343        Strophe.info("Disconnect was called because: " + reason);
2344        if (this.connected) {
2345            // setup timeout handler
2346            this._disconnectTimeout = this._addSysTimedHandler(
2347                3000, this._onDisconnectTimeout.bind(this));
2348            this._sendTerminate();
2349        }
2350    },
2351
2352    /** PrivateFunction: _changeConnectStatus
2353     *  _Private_ helper function that makes sure plugins and the user's
2354     *  callback are notified of connection status changes.
2355     *
2356     *  Parameters:
2357     *    (Integer) status - the new connection status, one of the values
2358     *      in Strophe.Status
2359     *    (String) condition - the error condition or null
2360     */
2361    _changeConnectStatus: function (status, condition)
2362    {
2363        // notify all plugins listening for status changes
2364        for (var k in Strophe._connectionPlugins) {
2365            if (Strophe._connectionPlugins.hasOwnProperty(k)) {
2366                var plugin = this[k];
2367                if (plugin.statusChanged) {
2368                    try {
2369                        plugin.statusChanged(status, condition);
2370                    } catch (err) {
2371                        Strophe.error("" + k + " plugin caused an exception " +
2372                                      "changing status: " + err);
2373                    }
2374                }
2375            }
2376        }
2377
2378        // notify the user's callback
2379        if (this.connect_callback) {
2380            try {
2381                this.connect_callback(status, condition);
2382            } catch (e) {
2383                Strophe.error("User connection callback caused an " +
2384                              "exception: " + e);
2385            }
2386        }
2387    },
2388
2389    /** PrivateFunction: _buildBody
2390     *  _Private_ helper function to generate the <body/> wrapper for BOSH.
2391     *
2392     *  Returns:
2393     *    A Strophe.Builder with a <body/> element.
2394     */
2395    _buildBody: function ()
2396    {
2397        var bodyWrap = $build('body', {
2398            rid: this.rid++,
2399            xmlns: Strophe.NS.HTTPBIND
2400        });
2401
2402        if (this.sid !== null) {
2403            bodyWrap.attrs({sid: this.sid});
2404        }
2405
2406        return bodyWrap;
2407    },
2408
2409    /** PrivateFunction: _removeRequest
2410     *  _Private_ function to remove a request from the queue.
2411     *
2412     *  Parameters:
2413     *    (Strophe.Request) req - The request to remove.
2414     */
2415    _removeRequest: function (req)
2416    {
2417        Strophe.debug("removing request");
2418
2419        var i;
2420        for (i = this._requests.length - 1; i >= 0; i--) {
2421            if (req == this._requests[i]) {
2422                this._requests.splice(i, 1);
2423            }
2424        }
2425
2426        // IE6 fails on setting to null, so set to empty function
2427        req.xhr.onreadystatechange = function () {};
2428
2429        this._throttledRequestHandler();
2430    },
2431
2432    /** PrivateFunction: _restartRequest
2433     *  _Private_ function to restart a request that is presumed dead.
2434     *
2435     *  Parameters:
2436     *    (Integer) i - The index of the request in the queue.
2437     */
2438    _restartRequest: function (i)
2439    {
2440        var req = this._requests[i];
2441        if (req.dead === null) {
2442            req.dead = new Date();
2443        }
2444
2445        this._processRequest(i);
2446    },
2447
2448    /** PrivateFunction: _processRequest
2449     *  _Private_ function to process a request in the queue.
2450     *
2451     *  This function takes requests off the queue and sends them and
2452     *  restarts dead requests.
2453     *
2454     *  Parameters:
2455     *    (Integer) i - The index of the request in the queue.
2456     */
2457    _processRequest: function (i)
2458    {
2459        var req = this._requests[i];
2460        var reqStatus = -1;
2461
2462        try {
2463            if (req.xhr.readyState == 4) {
2464                reqStatus = req.xhr.status;
2465            }
2466        } catch (e) {
2467            Strophe.error("caught an error in _requests[" + i +
2468                          "], reqStatus: " + reqStatus);
2469        }
2470
2471        if (typeof(reqStatus) == "undefined") {
2472            reqStatus = -1;
2473        }
2474
2475        var time_elapsed = req.age();
2476        var primaryTimeout = (!isNaN(time_elapsed) &&
2477                              time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait));
2478        var secondaryTimeout = (req.dead !== null &&
2479                                req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait));
2480        var requestCompletedWithServerError = (req.xhr.readyState == 4 &&
2481                                               (reqStatus < 1 ||
2482                                                reqStatus >= 500));
2483        if (primaryTimeout || secondaryTimeout ||
2484            requestCompletedWithServerError) {
2485            if (secondaryTimeout) {
2486                Strophe.error("Request " +
2487                              this._requests[i].id +
2488                              " timed out (secondary), restarting");
2489            }
2490            req.abort = true;
2491            req.xhr.abort();
2492            // setting to null fails on IE6, so set to empty function
2493            req.xhr.onreadystatechange = function () {};
2494            this._requests[i] = new Strophe.Request(req.xmlData,
2495                                                    req.origFunc,
2496                                                    req.rid,
2497                                                    req.sends);
2498            req = this._requests[i];
2499        }
2500
2501        if (req.xhr.readyState === 0) {
2502            Strophe.debug("request id " + req.id +
2503                          "." + req.sends + " posting");
2504
2505            req.date = new Date();
2506            try {
2507                req.xhr.open("POST", this.service, true);
2508            } catch (e2) {
2509                Strophe.error("XHR open failed.");
2510                if (!this.connected) {
2511                    this._changeConnectStatus(Strophe.Status.CONNFAIL,
2512                                              "bad-service");
2513                }
2514                this.disconnect();
2515                return;
2516            }
2517
2518            // Fires the XHR request -- may be invoked immediately
2519            // or on a gradually expanding retry window for reconnects
2520            var sendFunc = function () {
2521                req.xhr.send(req.data);
2522            };
2523
2524            // Implement progressive backoff for reconnects --
2525            // First retry (send == 1) should also be instantaneous
2526            if (req.sends > 1) {
2527                // Using a cube of the retry number creats a nicely
2528                // expanding retry window
2529                var backoff = Math.pow(req.sends, 3) * 1000;
2530                setTimeout(sendFunc, backoff);
2531            } else {
2532                sendFunc();
2533            }
2534
2535            req.sends++;
2536
2537            this.xmlOutput(req.xmlData);
2538            this.rawOutput(req.data);
2539        } else {
2540            Strophe.debug("_processRequest: " +
2541                          (i === 0 ? "first" : "second") +
2542                          " request has readyState of " +
2543                          req.xhr.readyState);
2544        }
2545    },
2546
2547    /** PrivateFunction: _throttledRequestHandler
2548     *  _Private_ function to throttle requests to the connection window.
2549     *
2550     *  This function makes sure we don't send requests so fast that the
2551     *  request ids overflow the connection window in the case that one
2552     *  request died.
2553     */
2554    _throttledRequestHandler: function ()
2555    {
2556        if (!this._requests) {
2557            Strophe.debug("_throttledRequestHandler called with " +
2558                          "undefined requests");
2559        } else {
2560            Strophe.debug("_throttledRequestHandler called with " +
2561                          this._requests.length + " requests");
2562        }
2563
2564        if (!this._requests || this._requests.length === 0) {
2565            return;
2566        }
2567
2568        if (this._requests.length > 0) {
2569            this._processRequest(0);
2570        }
2571
2572        if (this._requests.length > 1 &&
2573            Math.abs(this._requests[0].rid -
2574                     this._requests[1].rid) < this.window - 1) {
2575            this._processRequest(1);
2576        }
2577    },
2578
2579    /** PrivateFunction: _onRequestStateChange
2580     *  _Private_ handler for Strophe.Request state changes.
2581     *
2582     *  This function is called when the XMLHttpRequest readyState changes.
2583     *  It contains a lot of error handling logic for the many ways that
2584     *  requests can fail, and calls the request callback when requests
2585     *  succeed.
2586     *
2587     *  Parameters:
2588     *    (Function) func - The handler for the request.
2589     *    (Strophe.Request) req - The request that is changing readyState.
2590     */
2591    _onRequestStateChange: function (func, req)
2592    {
2593        Strophe.debug("request id " + req.id +
2594                      "." + req.sends + " state changed to " +
2595                      req.xhr.readyState);
2596
2597        if (req.abort) {
2598            req.abort = false;
2599            return;
2600        }
2601
2602        // request complete
2603        var reqStatus;
2604        if (req.xhr.readyState == 4) {
2605            reqStatus = 0;
2606            try {
2607                reqStatus = req.xhr.status;
2608            } catch (e) {
2609                // ignore errors from undefined status attribute.  works
2610                // around a browser bug
2611            }
2612
2613            if (typeof(reqStatus) == "undefined") {
2614                reqStatus = 0;
2615            }
2616
2617            if (this.disconnecting) {
2618                if (reqStatus >= 400) {
2619                    this._hitError(reqStatus);
2620                    return;
2621                }
2622            }
2623
2624            var reqIs0 = (this._requests[0] == req);
2625            var reqIs1 = (this._requests[1] == req);
2626
2627            if ((reqStatus > 0 && reqStatus < 500) || req.sends > 5) {
2628                // remove from internal queue
2629                this._removeRequest(req);
2630                Strophe.debug("request id " +
2631                              req.id +
2632                              " should now be removed");
2633            }
2634
2635            // request succeeded
2636            if (reqStatus == 200) {
2637                // if request 1 finished, or request 0 finished and request
2638                // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to
2639                // restart the other - both will be in the first spot, as the
2640                // completed request has been removed from the queue already
2641                if (reqIs1 ||
2642                    (reqIs0 && this._requests.length > 0 &&
2643                     this._requests[0].age() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait))) {
2644                    this._restartRequest(0);
2645                }
2646                // call handler
2647                Strophe.debug("request id " +
2648                              req.id + "." +
2649                              req.sends + " got 200");
2650                func(req);
2651                this.errors = 0;
2652            } else {
2653                Strophe.error("request id " +
2654                              req.id + "." +
2655                              req.sends + " error " + reqStatus +
2656                              " happened");
2657                if (reqStatus === 0 ||
2658                    (reqStatus >= 400 && reqStatus < 600) ||
2659                    reqStatus >= 12000) {
2660                    this._hitError(reqStatus);
2661                    if (reqStatus >= 400 && reqStatus < 500) {
2662                        this._changeConnectStatus(Strophe.Status.DISCONNECTING,
2663                                                  null);
2664                        this._doDisconnect();
2665                    }
2666                }
2667            }
2668
2669            if (!((reqStatus > 0 && reqStatus < 10000) ||
2670                  req.sends > 5)) {
2671                this._throttledRequestHandler();
2672            }
2673        }
2674    },
2675
2676    /** PrivateFunction: _hitError
2677     *  _Private_ function to handle the error count.
2678     *
2679     *  Requests are resent automatically until their error count reaches
2680     *  5.  Each time an error is encountered, this function is called to
2681     *  increment the count and disconnect if the count is too high.
2682     *
2683     *  Parameters:
2684     *    (Integer) reqStatus - The request status.
2685     */
2686    _hitError: function (reqStatus)
2687    {
2688        this.errors++;
2689        Strophe.warn("request errored, status: " + reqStatus +
2690                     ", number of errors: " + this.errors);
2691        if (this.errors > 4) {
2692            this._onDisconnectTimeout();
2693        }
2694    },
2695
2696    /** PrivateFunction: _doDisconnect
2697     *  _Private_ function to disconnect.
2698     *
2699     *  This is the last piece of the disconnection logic.  This resets the
2700     *  connection and alerts the user's connection callback.
2701     */
2702    _doDisconnect: function ()
2703    {
2704        Strophe.info("_doDisconnect was called");
2705        this.authenticated = false;
2706        this.disconnecting = false;
2707        this.sid = null;
2708        this.streamId = null;
2709        this.rid = Math.floor(Math.random() * 4294967295);
2710
2711        // tell the parent we disconnected
2712        if (this.connected) {
2713            this._changeConnectStatus(Strophe.Status.DISCONNECTED, null);
2714            this.connected = false;
2715        }
2716
2717        // delete handlers
2718        this.handlers = [];
2719        this.timedHandlers = [];
2720        this.removeTimeds = [];
2721        this.removeHandlers = [];
2722        this.addTimeds = [];
2723        this.addHandlers = [];
2724    },
2725
2726    /** PrivateFunction: _dataRecv
2727     *  _Private_ handler to processes incoming data from the the connection.
2728     *
2729     *  Except for _connect_cb handling the initial connection request,
2730     *  this function handles the incoming data for all requests.  This
2731     *  function also fires stanza handlers that match each incoming
2732     *  stanza.
2733     *
2734     *  Parameters:
2735     *    (Strophe.Request) req - The request that has data ready.
2736     */
2737    _dataRecv: function (req)
2738    {
2739        try {
2740            var elem = req.getResponse();
2741        } catch (e) {
2742            if (e != "parsererror") { throw e; }
2743            this.disconnect("strophe-parsererror");
2744        }
2745        if (elem === null) { return; }
2746
2747        this.xmlInput(elem);
2748        this.rawInput(Strophe.serialize(elem));
2749
2750        // remove handlers scheduled for deletion
2751        var i, hand;
2752        while (this.removeHandlers.length > 0) {
2753            hand = this.removeHandlers.pop();
2754            i = this.handlers.indexOf(hand);
2755            if (i >= 0) {
2756                this.handlers.splice(i, 1);
2757            }
2758        }
2759
2760        // add handlers scheduled for addition
2761        while (this.addHandlers.length > 0) {
2762            this.handlers.push(this.addHandlers.pop());
2763        }
2764
2765        // handle graceful disconnect
2766        if (this.disconnecting && this._requests.length === 0) {
2767            this.deleteTimedHandler(this._disconnectTimeout);
2768            this._disconnectTimeout = null;
2769            this._doDisconnect();
2770            return;
2771        }
2772
2773        var typ = elem.getAttribute("type");
2774        var cond, conflict;
2775        if (typ !== null && typ == "terminate") {
2776            // an error occurred
2777            cond = elem.getAttribute("condition");
2778            conflict = elem.getElementsByTagName("conflict");
2779            if (cond !== null) {
2780                if (cond == "remote-stream-error" && conflict.length > 0) {
2781                    cond = "conflict";
2782                }
2783                this._changeConnectStatus(Strophe.Status.CONNFAIL, cond);
2784            } else {
2785                this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown");
2786            }
2787            this.disconnect();
2788            return;
2789        }
2790
2791        // send each incoming stanza through the handler chain
2792        var self = this;
2793        Strophe.forEachChild(elem, null, function (child) {
2794            var i, newList;
2795            // process handlers
2796            newList = self.handlers;
2797            self.handlers = [];
2798            for (i = 0; i < newList.length; i++) {
2799                var hand = newList[i];
2800                if (hand.isMatch(child) &&
2801                    (self.authenticated || !hand.user)) {
2802                    if (hand.run(child)) {
2803                        self.handlers.push(hand);
2804                    }
2805                } else {
2806                    self.handlers.push(hand);
2807                }
2808            }
2809        });
2810    },
2811
2812    /** PrivateFunction: _sendTerminate
2813     *  _Private_ function to send initial disconnect sequence.
2814     *
2815     *  This is the first step in a graceful disconnect.  It sends
2816     *  the BOSH server a terminate body and includes an unavailable
2817     *  presence if authentication has completed.
2818     */
2819    _sendTerminate: function ()
2820    {
2821        Strophe.info("_sendTerminate was called");
2822        var body = this._buildBody().attrs({type: "terminate"});
2823
2824        if (this.authenticated) {
2825            body.c('presence', {
2826                xmlns: Strophe.NS.CLIENT,
2827                type: 'unavailable'
2828            });
2829        }
2830
2831        this.disconnecting = true;
2832
2833        var req = new Strophe.Request(body.tree(),
2834                                      this._onRequestStateChange.bind(this)
2835                                          .prependArg(this._dataRecv.bind(this)),
2836                                      body.tree().getAttribute("rid"));
2837
2838        this._requests.push(req);
2839        this._throttledRequestHandler();
2840    },
2841
2842    /** PrivateFunction: _connect_cb
2843     *  _Private_ handler for initial connection request.
2844     *
2845     *  This handler is used to process the initial connection request
2846     *  response from the BOSH server. It is used to set up authentication
2847     *  handlers and start the authentication process.
2848     *
2849     *  SASL authentication will be attempted if available, otherwise
2850     *  the code will fall back to legacy authentication.
2851     *
2852     *  Parameters:
2853     *    (Strophe.Request) req - The current request.
2854     */
2855    _connect_cb: function (req)
2856    {
2857        Strophe.info("_connect_cb was called");
2858
2859        this.connected = true;
2860        var bodyWrap = req.getResponse();
2861        if (!bodyWrap) { return; }
2862
2863        this.xmlInput(bodyWrap);
2864        this.rawInput(Strophe.serialize(bodyWrap));
2865
2866        var typ = bodyWrap.getAttribute("type");
2867        var cond, conflict;
2868        if (typ !== null && typ == "terminate") {
2869            // an error occurred
2870            cond = bodyWrap.getAttribute("condition");
2871            conflict = bodyWrap.getElementsByTagName("conflict");
2872            if (cond !== null) {
2873                if (cond == "remote-stream-error" && conflict.length > 0) {
2874                    cond = "conflict";
2875                }
2876                this._changeConnectStatus(Strophe.Status.CONNFAIL, cond);
2877            } else {
2878                this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown");
2879            }
2880            return;
2881        }
2882
2883        // check to make sure we don't overwrite these if _connect_cb is
2884        // called multiple times in the case of missing stream:features
2885        if (!this.sid) {
2886            this.sid = bodyWrap.getAttribute("sid");
2887        }
2888        if (!this.stream_id) {
2889            this.stream_id = bodyWrap.getAttribute("authid");
2890        }
2891        var wind = bodyWrap.getAttribute('requests');
2892        if (wind) { this.window = parseInt(wind, 10); }
2893        var hold = bodyWrap.getAttribute('hold');
2894        if (hold) { this.hold = parseInt(hold, 10); }
2895        var wait = bodyWrap.getAttribute('wait');
2896        if (wait) { this.wait = parseInt(wait, 10); }
2897       
2898
2899        var do_sasl_plain = false;
2900        var do_sasl_digest_md5 = false;
2901        var do_sasl_anonymous = false;
2902
2903        var mechanisms = bodyWrap.getElementsByTagName("mechanism");
2904        var i, mech, auth_str, hashed_auth_str;
2905        if (mechanisms.length > 0) {
2906            for (i = 0; i < mechanisms.length; i++) {
2907                mech = Strophe.getText(mechanisms[i]);
2908                if (mech == 'DIGEST-MD5') {
2909                    do_sasl_digest_md5 = true;
2910                } else if (mech == 'PLAIN') {
2911                    do_sasl_plain = true;
2912                } else if (mech == 'ANONYMOUS') {
2913                    do_sasl_anonymous = true;
2914                }
2915            }
2916        } else {
2917            // we didn't get stream:features yet, so we need wait for it
2918            // by sending a blank poll request
2919            var body = this._buildBody();
2920            this._requests.push(
2921                new Strophe.Request(body.tree(),
2922                                    this._onRequestStateChange.bind(this)
2923                                      .prependArg(this._connect_cb.bind(this)),
2924                                    body.tree().getAttribute("rid")));
2925            this._throttledRequestHandler();
2926            return;
2927        }
2928
2929        if (Strophe.getNodeFromJid(this.jid) === null &&
2930            do_sasl_anonymous) {
2931            this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
2932            this._sasl_success_handler = this._addSysHandler(
2933                this._sasl_success_cb.bind(this), null,
2934                "success", null, null);
2935            this._sasl_failure_handler = this._addSysHandler(
2936                this._sasl_failure_cb.bind(this), null,
2937                "failure", null, null);
2938
2939            this.send($build("auth", {
2940                xmlns: Strophe.NS.SASL,
2941                mechanism: "ANONYMOUS"
2942            }).tree());
2943        } else if (Strophe.getNodeFromJid(this.jid) === null) {
2944            // we don't have a node, which is required for non-anonymous
2945            // client connections
2946            this._changeConnectStatus(Strophe.Status.CONNFAIL,
2947                                      'x-strophe-bad-non-anon-jid');
2948            this.disconnect();
2949        } else if (do_sasl_digest_md5) {
2950            this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
2951            this._sasl_challenge_handler = this._addSysHandler(
2952                this._sasl_challenge1_cb.bind(this), null,
2953                "challenge", null, null);
2954            this._sasl_failure_handler = this._addSysHandler(
2955                this._sasl_failure_cb.bind(this), null,
2956                "failure", null, null);
2957
2958            this.send($build("auth", {
2959                xmlns: Strophe.NS.SASL,
2960                mechanism: "DIGEST-MD5"
2961            }).tree());
2962        } else if (do_sasl_plain) {
2963            // Build the plain auth string (barejid null
2964            // username null password) and base 64 encoded.
2965            auth_str = Strophe.getBareJidFromJid(this.jid);
2966            auth_str = auth_str + "\u0000";
2967            auth_str = auth_str + Strophe.getNodeFromJid(this.jid);
2968            auth_str = auth_str + "\u0000";
2969            auth_str = auth_str + this.pass;
2970
2971            this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
2972            this._sasl_success_handler = this._addSysHandler(
2973                this._sasl_success_cb.bind(this), null,
2974                "success", null, null);
2975            this._sasl_failure_handler = this._addSysHandler(
2976                this._sasl_failure_cb.bind(this), null,
2977                "failure", null, null);
2978
2979            hashed_auth_str = Base64.encode(auth_str);
2980            this.send($build("auth", {
2981                xmlns: Strophe.NS.SASL,
2982                mechanism: "PLAIN"
2983            }).t(hashed_auth_str).tree());
2984        } else {
2985            this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
2986            this._addSysHandler(this._auth1_cb.bind(this), null, null,
2987                                null, "_auth_1");
2988
2989            this.send($iq({
2990                type: "get",
2991                to: this.domain,
2992                id: "_auth_1"
2993            }).c("query", {
2994                xmlns: Strophe.NS.AUTH
2995            }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).tree());
2996        }
2997    },
2998
2999    /** PrivateFunction: _sasl_challenge1_cb
3000     *  _Private_ handler for DIGEST-MD5 SASL authentication.
3001     *
3002     *  Parameters:
3003     *    (XMLElement) elem - The challenge stanza.
3004     *
3005     *  Returns:
3006     *    false to remove the handler.
3007     */
3008    _sasl_challenge1_cb: function (elem)
3009    {
3010        var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;
3011
3012        var challenge = Base64.decode(Strophe.getText(elem));
3013        var cnonce = MD5.hexdigest(Math.random() * 1234567890);
3014        var realm = "";
3015        var host = null;
3016        var nonce = "";
3017        var qop = "";
3018        var matches;
3019
3020        // remove unneeded handlers
3021        this.deleteHandler(this._sasl_failure_handler);
3022
3023        while (challenge.match(attribMatch)) {
3024            matches = challenge.match(attribMatch);
3025            challenge = challenge.replace(matches[0], "");
3026            matches[2] = matches[2].replace(/^"(.+)"$/, "$1");
3027            switch (matches[1]) {
3028            case "realm":
3029                realm = matches[2];
3030                break;
3031            case "nonce":
3032                nonce = matches[2];
3033                break;
3034            case "qop":
3035                qop = matches[2];
3036                break;
3037            case "host":
3038                host = matches[2];
3039                break;
3040            }
3041        }
3042
3043        var digest_uri = "xmpp/" + this.domain;
3044        if (host !== null) {
3045            digest_uri = digest_uri + "/" + host;
3046        }
3047
3048        var A1 = MD5.hash(Strophe.getNodeFromJid(this.jid) +
3049                          ":" + realm + ":" + this.pass) +
3050            ":" + nonce + ":" + cnonce;
3051        var A2 = 'AUTHENTICATE:' + digest_uri;
3052
3053        var responseText = "";
3054        responseText += 'username=' +
3055            this._quote(Strophe.getNodeFromJid(this.jid)) + ',';
3056        responseText += 'realm=' + this._quote(realm) + ',';
3057        responseText += 'nonce=' + this._quote(nonce) + ',';
3058        responseText += 'cnonce=' + this._quote(cnonce) + ',';
3059        responseText += 'nc="00000001",';
3060        responseText += 'qop="auth",';
3061        responseText += 'digest-uri=' + this._quote(digest_uri) + ',';
3062        responseText += 'response=' + this._quote(
3063            MD5.hexdigest(MD5.hexdigest(A1) + ":" +
3064                          nonce + ":00000001:" +
3065                          cnonce + ":auth:" +
3066                          MD5.hexdigest(A2))) + ',';
3067        responseText += 'charset="utf-8"';
3068
3069        this._sasl_challenge_handler = this._addSysHandler(
3070            this._sasl_challenge2_cb.bind(this), null,
3071            "challenge", null, null);
3072        this._sasl_success_handler = this._addSysHandler(
3073            this._sasl_success_cb.bind(this), null,
3074            "success", null, null);
3075        this._sasl_failure_handler = this._addSysHandler(
3076            this._sasl_failure_cb.bind(this), null,
3077            "failure", null, null);
3078
3079        this.send($build('response', {
3080            xmlns: Strophe.NS.SASL
3081        }).t(Base64.encode(responseText)).tree());
3082
3083        return false;
3084    },
3085
3086    /** PrivateFunction: _quote
3087     *  _Private_ utility function to backslash escape and quote strings.
3088     *
3089     *  Parameters:
3090     *    (String) str - The string to be quoted.
3091     *
3092     *  Returns:
3093     *    quoted string
3094     */
3095    _quote: function (str)
3096    {
3097        return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
3098        //" end string workaround for emacs
3099    },
3100
3101
3102    /** PrivateFunction: _sasl_challenge2_cb
3103     *  _Private_ handler for second step of DIGEST-MD5 SASL authentication.
3104     *
3105     *  Parameters:
3106     *    (XMLElement) elem - The challenge stanza.
3107     *
3108     *  Returns:
3109     *    false to remove the handler.
3110     */
3111    _sasl_challenge2_cb: function (elem)
3112    {
3113        // remove unneeded handlers
3114        this.deleteHandler(this._sasl_success_handler);
3115        this.deleteHandler(this._sasl_failure_handler);
3116
3117        this._sasl_success_handler = this._addSysHandler(
3118            this._sasl_success_cb.bind(this), null,
3119            "success", null, null);
3120        this._sasl_failure_handler = this._addSysHandler(
3121            this._sasl_failure_cb.bind(this), null,
3122            "failure", null, null);
3123        this.send($build('response', {xmlns: Strophe.NS.SASL}).tree());
3124        return false;
3125    },
3126
3127    /** PrivateFunction: _auth1_cb
3128     *  _Private_ handler for legacy authentication.
3129     *
3130     *  This handler is called in response to the initial <iq type='get'/>
3131     *  for legacy authentication.  It builds an authentication <iq/> and
3132     *  sends it, creating a handler (calling back to _auth2_cb()) to
3133     *  handle the result
3134     *
3135     *  Parameters:
3136     *    (XMLElement) elem - The stanza that triggered the callback.
3137     *
3138     *  Returns:
3139     *    false to remove the handler.
3140     */
3141    _auth1_cb: function (elem)
3142    {
3143        // build plaintext auth iq
3144        var iq = $iq({type: "set", id: "_auth_2"})
3145            .c('query', {xmlns: Strophe.NS.AUTH})
3146            .c('username', {}).t(Strophe.getNodeFromJid(this.jid))
3147            .up()
3148            .c('password').t(this.pass);
3149
3150        if (!Strophe.getResourceFromJid(this.jid)) {
3151            // since the user has not supplied a resource, we pick
3152            // a default one here.  unlike other auth methods, the server
3153            // cannot do this for us.
3154            this.jid = Strophe.getBareJidFromJid(this.jid) + '/strophe';
3155        }
3156        iq.up().c('resource', {}).t(Strophe.getResourceFromJid(this.jid));
3157
3158        this._addSysHandler(this._auth2_cb.bind(this), null,
3159                            null, null, "_auth_2");
3160
3161        this.send(iq.tree());
3162
3163        return false;
3164    },
3165
3166    /** PrivateFunction: _sasl_success_cb
3167     *  _Private_ handler for succesful SASL authentication.
3168     *
3169     *  Parameters:
3170     *    (XMLElement) elem - The matching stanza.
3171     *
3172     *  Returns:
3173     *    false to remove the handler.
3174     */
3175    _sasl_success_cb: function (elem)
3176    {
3177        Strophe.info("SASL authentication succeeded.");
3178
3179        // remove old handlers
3180        this.deleteHandler(this._sasl_failure_handler);
3181        this._sasl_failure_handler = null;
3182        if (this._sasl_challenge_handler) {
3183            this.deleteHandler(this._sasl_challenge_handler);
3184            this._sasl_challenge_handler = null;
3185        }
3186
3187        this._addSysHandler(this._sasl_auth1_cb.bind(this), null,
3188                            "stream:features", null, null);
3189
3190        // we must send an xmpp:restart now
3191        this._sendRestart();
3192
3193        return false;
3194    },
3195
3196    /** PrivateFunction: _sasl_auth1_cb
3197     *  _Private_ handler to start stream binding.
3198     *
3199     *  Parameters:
3200     *    (XMLElement) elem - The matching stanza.
3201     *
3202     *  Returns:
3203     *    false to remove the handler.
3204     */
3205    _sasl_auth1_cb: function (elem)
3206    {
3207        var i, child;
3208
3209        for (i = 0; i < elem.childNodes.length; i++) {
3210            child = elem.childNodes[i];
3211            if (child.nodeName == 'bind') {
3212                this.do_bind = true;
3213            }
3214
3215            if (child.nodeName == 'session') {
3216                this.do_session = true;
3217            }
3218        }
3219
3220        if (!this.do_bind) {
3221            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3222            return false;
3223        } else {
3224            this._addSysHandler(this._sasl_bind_cb.bind(this), null, null,
3225                                null, "_bind_auth_2");
3226
3227            var resource = Strophe.getResourceFromJid(this.jid);
3228            if (resource) {
3229                this.send($iq({type: "set", id: "_bind_auth_2"})
3230                          .c('bind', {xmlns: Strophe.NS.BIND})
3231                          .c('resource', {}).t(resource).tree());
3232            } else {
3233                this.send($iq({type: "set", id: "_bind_auth_2"})
3234                          .c('bind', {xmlns: Strophe.NS.BIND})
3235                          .tree());
3236            }
3237        }
3238
3239        return false;
3240    },
3241
3242    /** PrivateFunction: _sasl_bind_cb
3243     *  _Private_ handler for binding result and session start.
3244     *
3245     *  Parameters:
3246     *    (XMLElement) elem - The matching stanza.
3247     *
3248     *  Returns:
3249     *    false to remove the handler.
3250     */
3251    _sasl_bind_cb: function (elem)
3252    {
3253        if (elem.getAttribute("type") == "error") {
3254            Strophe.info("SASL binding failed.");
3255            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3256            return false;
3257        }
3258
3259        // TODO - need to grab errors
3260        var bind = elem.getElementsByTagName("bind");
3261        var jidNode;
3262        if (bind.length > 0) {
3263            // Grab jid
3264            jidNode = bind[0].getElementsByTagName("jid");
3265            if (jidNode.length > 0) {
3266                this.jid = Strophe.getText(jidNode[0]);
3267
3268                if (this.do_session) {
3269                    this._addSysHandler(this._sasl_session_cb.bind(this),
3270                                        null, null, null, "_session_auth_2");
3271
3272                    this.send($iq({type: "set", id: "_session_auth_2"})
3273                                  .c('session', {xmlns: Strophe.NS.SESSION})
3274                                  .tree());
3275                } else {
3276                    this.authenticated = true;
3277                    this._changeConnectStatus(Strophe.Status.CONNECTED, null);
3278                }
3279            }
3280        } else {
3281            Strophe.info("SASL binding failed.");
3282            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3283            return false;
3284        }
3285    },
3286
3287    /** PrivateFunction: _sasl_session_cb
3288     *  _Private_ handler to finish successful SASL connection.
3289     *
3290     *  This sets Connection.authenticated to true on success, which
3291     *  starts the processing of user handlers.
3292     *
3293     *  Parameters:
3294     *    (XMLElement) elem - The matching stanza.
3295     *
3296     *  Returns:
3297     *    false to remove the handler.
3298     */
3299    _sasl_session_cb: function (elem)
3300    {
3301        if (elem.getAttribute("type") == "result") {
3302            this.authenticated = true;
3303            this._changeConnectStatus(Strophe.Status.CONNECTED, null);
3304        } else if (elem.getAttribute("type") == "error") {
3305            Strophe.info("Session creation failed.");
3306            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3307            return false;
3308        }
3309
3310        return false;
3311    },
3312
3313    /** PrivateFunction: _sasl_failure_cb
3314     *  _Private_ handler for SASL authentication failure.
3315     *
3316     *  Parameters:
3317     *    (XMLElement) elem - The matching stanza.
3318     *
3319     *  Returns:
3320     *    false to remove the handler.
3321     */
3322    _sasl_failure_cb: function (elem)
3323    {
3324        // delete unneeded handlers
3325        if (this._sasl_success_handler) {
3326            this.deleteHandler(this._sasl_success_handler);
3327            this._sasl_success_handler = null;
3328        }
3329        if (this._sasl_challenge_handler) {
3330            this.deleteHandler(this._sasl_challenge_handler);
3331            this._sasl_challenge_handler = null;
3332        }
3333
3334        this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3335        return false;
3336    },
3337
3338    /** PrivateFunction: _auth2_cb
3339     *  _Private_ handler to finish legacy authentication.
3340     *
3341     *  This handler is called when the result from the jabber:iq:auth
3342     *  <iq/> stanza is returned.
3343     *
3344     *  Parameters:
3345     *    (XMLElement) elem - The stanza that triggered the callback.
3346     *
3347     *  Returns:
3348     *    false to remove the handler.
3349     */
3350    _auth2_cb: function (elem)
3351    {
3352        if (elem.getAttribute("type") == "result") {
3353            this.authenticated = true;
3354            this._changeConnectStatus(Strophe.Status.CONNECTED, null);
3355        } else if (elem.getAttribute("type") == "error") {
3356            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3357            this.disconnect();
3358        }
3359
3360        return false;
3361    },
3362
3363    /** PrivateFunction: _addSysTimedHandler
3364     *  _Private_ function to add a system level timed handler.
3365     *
3366     *  This function is used to add a Strophe.TimedHandler for the
3367     *  library code.  System timed handlers are allowed to run before
3368     *  authentication is complete.
3369     *
3370     *  Parameters:
3371     *    (Integer) period - The period of the handler.
3372     *    (Function) handler - The callback function.
3373     */
3374    _addSysTimedHandler: function (period, handler)
3375    {
3376        var thand = new Strophe.TimedHandler(period, handler);
3377        thand.user = false;
3378        this.addTimeds.push(thand);
3379        return thand;
3380    },
3381
3382    /** PrivateFunction: _addSysHandler
3383     *  _Private_ function to add a system level stanza handler.
3384     *
3385     *  This function is used to add a Strophe.Handler for the
3386     *  library code.  System stanza handlers are allowed to run before
3387     *  authentication is complete.
3388     *
3389     *  Parameters:
3390     *    (Function) handler - The callback function.
3391     *    (String) ns - The namespace to match.
3392     *    (String) name - The stanza name to match.
3393     *    (String) type - The stanza type attribute to match.
3394     *    (String) id - The stanza id attribute to match.
3395     */
3396    _addSysHandler: function (handler, ns, name, type, id)
3397    {
3398        var hand = new Strophe.Handler(handler, ns, name, type, id);
3399        hand.user = false;
3400        this.addHandlers.push(hand);
3401        return hand;
3402    },
3403
3404    /** PrivateFunction: _onDisconnectTimeout
3405     *  _Private_ timeout handler for handling non-graceful disconnection.
3406     *
3407     *  If the graceful disconnect process does not complete within the
3408     *  time allotted, this handler finishes the disconnect anyway.
3409     *
3410     *  Returns:
3411     *    false to remove the handler.
3412     */
3413    _onDisconnectTimeout: function ()
3414    {
3415        Strophe.info("_onDisconnectTimeout was called");
3416
3417        // cancel all remaining requests and clear the queue
3418        var req;
3419        while (this._requests.length > 0) {
3420            req = this._requests.pop();
3421            req.abort = true;
3422            req.xhr.abort();
3423            // jslint complains, but this is fine. setting to empty func
3424            // is necessary for IE6
3425            req.xhr.onreadystatechange = function () {};
3426        }
3427
3428        // actually disconnect
3429        this._doDisconnect();
3430
3431        return false;
3432    },
3433
3434    /** PrivateFunction: _onIdle
3435     *  _Private_ handler to process events during idle cycle.
3436     *
3437     *  This handler is called every 100ms to fire timed handlers that
3438     *  are ready and keep poll requests going.
3439     */
3440    _onIdle: function ()
3441    {
3442        var i, thand, since, newList;
3443
3444        // remove timed handlers that have been scheduled for deletion
3445        while (this.removeTimeds.length > 0) {
3446            thand = this.removeTimeds.pop();
3447            i = this.timedHandlers.indexOf(thand);
3448            if (i >= 0) {
3449                this.timedHandlers.splice(i, 1);
3450            }
3451        }
3452
3453        // add timed handlers scheduled for addition
3454        while (this.addTimeds.length > 0) {
3455            this.timedHandlers.push(this.addTimeds.pop());
3456        }
3457
3458        // call ready timed handlers
3459        var now = new Date().getTime();
3460        newList = [];
3461        for (i = 0; i < this.timedHandlers.length; i++) {
3462            thand = this.timedHandlers[i];
3463            if (this.authenticated || !thand.user) {
3464                since = thand.lastCalled + thand.period;
3465                if (since - now <= 0) {
3466                    if (thand.run()) {
3467                        newList.push(thand);
3468                    }
3469                } else {
3470                    newList.push(thand);
3471                }
3472            }
3473        }
3474        this.timedHandlers = newList;
3475
3476        var body, time_elapsed;
3477
3478        // if no requests are in progress, poll
3479        if (this.authenticated && this._requests.length === 0 &&
3480            this._data.length === 0 && !this.disconnecting) {
3481            Strophe.info("no requests during idle cycle, sending " +
3482                         "blank request");
3483            this._data.push(null);
3484        }
3485
3486        if (this._requests.length < 2 && this._data.length > 0 &&
3487            !this.paused) {
3488            body = this._buildBody();
3489            for (i = 0; i < this._data.length; i++) {
3490                if (this._data[i] !== null) {
3491                    if (this._data[i] === "restart") {
3492                        body.attrs({
3493                            to: this.domain,
3494                            "xml:lang": "en",
3495                            "xmpp:restart": "true",
3496                            "xmlns:xmpp": Strophe.NS.BOSH
3497                        });
3498                    } else {
3499                        body.cnode(this._data[i]).up();
3500                    }
3501                }
3502            }
3503            delete this._data;
3504            this._data = [];
3505            this._requests.push(
3506                new Strophe.Request(body.tree(),
3507                                    this._onRequestStateChange.bind(this)
3508                                    .prependArg(this._dataRecv.bind(this)),
3509                                    body.tree().getAttribute("rid")));
3510            this._processRequest(this._requests.length - 1);
3511        }
3512
3513        if (this._requests.length > 0) {
3514            time_elapsed = this._requests[0].age();
3515            if (this._requests[0].dead !== null) {
3516                if (this._requests[0].timeDead() >
3517                    Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) {
3518                    this._throttledRequestHandler();
3519                }
3520            }
3521
3522            if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) {
3523                Strophe.warn("Request " +
3524                             this._requests[0].id +
3525                             " timed out, over " + Math.floor(Strophe.TIMEOUT * this.wait) +
3526                             " seconds since last activity");
3527                this._throttledRequestHandler();
3528            }
3529        }
3530
3531        // reactivate the timer
3532        clearTimeout(this._idleTimeout);
3533        this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
3534    }
3535};
3536
3537if (callback) {
3538    callback(Strophe, $build, $msg, $iq, $pres);
3539}
3540
3541})(function () {
3542    window.Strophe = arguments[0];
3543    window.$build = arguments[1];
3544    window.$msg = arguments[2];
3545    window.$iq = arguments[3];
3546    window.$pres = arguments[4];
3547});
Note: See TracBrowser for help on using the repository browser.