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

Revision 3102, 115.3 KB checked in by amuller, 14 years ago (diff)

Ticket #986 - Efetuado merge para o Branch 2.2( atualizacao do modulo)

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