Openfire Strophe开发中文乱码问题

网站上有很多Openfire Web方案,之前想用Smack 但是jar包支持客户端版本的,还有JDK版本问题  一直没调试成功  估计成功的方法只能拜读源码进行修改了。

SparkWeb 官网代码很久没维护  CSDN上下来个版本但jar包路径不对  花了不少时间总算能跑起来,不过版本是flex3版本,太老了   自己花精力升级有点费时间呀

最后采用存脚本开发Strophejs,下面网站写的很详细

学习的网站:http://www.dotblogs.com.tw/sungnoone/archive/2014/06/20/145642.aspx

Strophejs中文发送到服务器端老会出现乱码问题,这个问题网上也没好的解决方案,在这里我分享下我的方法。

1、修改Strophe.js

添加chencode中文编码,cht IQ请求节点中文内容,添加utf16to8方法

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

3496     {
3497         if (elem.getAttribute("type") == "result") {
3498             this.authenticated = true;
3499             this._changeConnectStatus(Strophe.Status.CONNECTED, null);
3500         } else if (elem.getAttribute("type") == "error") {
3501             Strophe.info("Session creation failed.");
3502             this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3503             return false;
3504         }
3505
3506         return false;
3507     },
3508
3509     /** PrivateFunction: _sasl_failure_cb
3510      *  _Private_ handler for SASL authentication failure.
3511      *
3512      *  Parameters:
3513      *    (XMLElement) elem - The matching stanza.
3514      *
3515      *  Returns:
3516      *    false to remove the handler.
3517      */
3518     /* jshint unused:false */
3519     _sasl_failure_cb: function (elem)
3520     {
3521         // delete unneeded handlers
3522         if (this._sasl_success_handler) {
3523             this.deleteHandler(this._sasl_success_handler);
3524             this._sasl_success_handler = null;
3525         }
3526         if (this._sasl_challenge_handler) {
3527             this.deleteHandler(this._sasl_challenge_handler);
3528             this._sasl_challenge_handler = null;
3529         }
3530
3531         if(this._sasl_mechanism)
3532           this._sasl_mechanism.onFailure();
3533         this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3534         return false;
3535     },
3536     /* jshint unused:true */
3537
3538     /** PrivateFunction: _auth2_cb
3539      *  _Private_ handler to finish legacy authentication.
3540      *
3541      *  This handler is called when the result from the jabber:iq:auth
3542      *  <iq/> stanza is returned.
3543      *
3544      *  Parameters:
3545      *    (XMLElement) elem - The stanza that triggered the callback.
3546      *
3547      *  Returns:
3548      *    false to remove the handler.
3549      */
3550     _auth2_cb: function (elem)
3551     {
3552         if (elem.getAttribute("type") == "result") {
3553             this.authenticated = true;
3554             this._changeConnectStatus(Strophe.Status.CONNECTED, null);
3555         } else if (elem.getAttribute("type") == "error") {
3556             this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3557             this.disconnect(‘authentication failed‘);
3558         }
3559
3560         return false;
3561     },
3562
3563     /** PrivateFunction: _addSysTimedHandler
3564      *  _Private_ function to add a system level timed handler.
3565      *
3566      *  This function is used to add a Strophe.TimedHandler for the
3567      *  library code.  System timed handlers are allowed to run before
3568      *  authentication is complete.
3569      *
3570      *  Parameters:
3571      *    (Integer) period - The period of the handler.
3572      *    (Function) handler - The callback function.
3573      */
3574     _addSysTimedHandler: function (period, handler)
3575     {
3576         var thand = new Strophe.TimedHandler(period, handler);
3577         thand.user = false;
3578         this.addTimeds.push(thand);
3579         return thand;
3580     },
3581
3582     /** PrivateFunction: _addSysHandler
3583      *  _Private_ function to add a system level stanza handler.
3584      *
3585      *  This function is used to add a Strophe.Handler for the
3586      *  library code.  System stanza handlers are allowed to run before
3587      *  authentication is complete.
3588      *
3589      *  Parameters:
3590      *    (Function) handler - The callback function.
3591      *    (String) ns - The namespace to match.
3592      *    (String) name - The stanza name to match.
3593      *    (String) type - The stanza type attribute to match.
3594      *    (String) id - The stanza id attribute to match.
3595      */
3596     _addSysHandler: function (handler, ns, name, type, id)
3597     {
3598         var hand = new Strophe.Handler(handler, ns, name, type, id);
3599         hand.user = false;
3600         this.addHandlers.push(hand);
3601         return hand;
3602     },
3603
3604     /** PrivateFunction: _onDisconnectTimeout
3605      *  _Private_ timeout handler for handling non-graceful disconnection.
3606      *
3607      *  If the graceful disconnect process does not complete within the
3608      *  time allotted, this handler finishes the disconnect anyway.
3609      *
3610      *  Returns:
3611      *    false to remove the handler.
3612      */
3613     _onDisconnectTimeout: function ()
3614     {
3615         Strophe.info("_onDisconnectTimeout was called");
3616
3617         this._proto._onDisconnectTimeout();
3618
3619         // actually disconnect
3620         this._doDisconnect();
3621
3622         return false;
3623     },
3624
3625     /** PrivateFunction: _onIdle
3626      *  _Private_ handler to process events during idle cycle.
3627      *
3628      *  This handler is called every 100ms to fire timed handlers that
3629      *  are ready and keep poll requests going.
3630      */
3631     _onIdle: function ()
3632     {
3633         var i, thand, since, newList;
3634
3635         // add timed handlers scheduled for addition
3636         // NOTE: we add before remove in the case a timed handler is
3637         // added and then deleted before the next _onIdle() call.
3638         while (this.addTimeds.length > 0) {
3639             this.timedHandlers.push(this.addTimeds.pop());
3640         }
3641
3642         // remove timed handlers that have been scheduled for deletion
3643         while (this.removeTimeds.length > 0) {
3644             thand = this.removeTimeds.pop();
3645             i = this.timedHandlers.indexOf(thand);
3646             if (i >= 0) {
3647                 this.timedHandlers.splice(i, 1);
3648             }
3649         }
3650
3651         // call ready timed handlers
3652         var now = new Date().getTime();
3653         newList = [];
3654         for (i = 0; i < this.timedHandlers.length; i++) {
3655             thand = this.timedHandlers[i];
3656             if (this.authenticated || !thand.user) {
3657                 since = thand.lastCalled + thand.period;
3658                 if (since - now <= 0) {
3659                     if (thand.run()) {
3660                         newList.push(thand);
3661                     }
3662                 } else {
3663                     newList.push(thand);
3664                 }
3665             }
3666         }
3667         this.timedHandlers = newList;
3668
3669         clearTimeout(this._idleTimeout);
3670
3671         this._proto._onIdle();
3672
3673         // reactivate the timer only if connected
3674         if (this.connected) {
3675             this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
3676         }
3677     }
3678 };
3679
3680 /** Class: Strophe.SASLMechanism
3681  *
3682  *  encapsulates SASL authentication mechanisms.
3683  *
3684  *  User code may override the priority for each mechanism or disable it completely.
3685  *  See <priority> for information about changing priority and <test> for informatian on
3686  *  how to disable a mechanism.
3687  *
3688  *  By default, all mechanisms are enabled and the priorities are
3689  *
3690  *  SCRAM-SHA1 - 40
3691  *  DIGEST-MD5 - 30
3692  *  Plain - 20
3693  */
3694
3695 /**
3696  * PrivateConstructor: Strophe.SASLMechanism
3697  * SASL auth mechanism abstraction.
3698  *
3699  *  Parameters:
3700  *    (String) name - SASL Mechanism name.
3701  *    (Boolean) isClientFirst - If client should send response first without challenge.
3702  *    (Number) priority - Priority.
3703  *
3704  *  Returns:
3705  *    A new Strophe.SASLMechanism object.
3706  */
3707 Strophe.SASLMechanism = function(name, isClientFirst, priority) {
3708   /** PrivateVariable: name
3709    *  Mechanism name.
3710    */
3711   this.name = name;
3712   /** PrivateVariable: isClientFirst
3713    *  If client sends response without initial server challenge.
3714    */
3715   this.isClientFirst = isClientFirst;
3716   /** Variable: priority
3717    *  Determines which <SASLMechanism> is chosen for authentication (Higher is better).
3718    *  Users may override this to prioritize mechanisms differently.
3719    *
3720    *  In the default configuration the priorities are
3721    *
3722    *  SCRAM-SHA1 - 40
3723    *  DIGEST-MD5 - 30
3724    *  Plain - 20
3725    *
3726    *  Example: (This will cause Strophe to choose the mechanism that the server sent first)
3727    *
3728    *  > Strophe.SASLMD5.priority = Strophe.SASLSHA1.priority;
3729    *
3730    *  See <SASL mechanisms> for a list of available mechanisms.
3731    *
3732    */
3733   this.priority = priority;
3734 };
3735
3736 Strophe.SASLMechanism.prototype = {
3737   /**
3738    *  Function: test
3739    *  Checks if mechanism able to run.
3740    *  To disable a mechanism, make this return false;
3741    *
3742    *  To disable plain authentication run
3743    *  > Strophe.SASLPlain.test = function() {
3744    *  >   return false;
3745    *  > }
3746    *
3747    *  See <SASL mechanisms> for a list of available mechanisms.
3748    *
3749    *  Parameters:
3750    *    (Strophe.Connection) connection - Target Connection.
3751    *
3752    *  Returns:
3753    *    (Boolean) If mechanism was able to run.
3754    */
3755   /* jshint unused:false */
3756   test: function(connection) {
3757     return true;
3758   },
3759   /* jshint unused:true */
3760
3761   /** PrivateFunction: onStart
3762    *  Called before starting mechanism on some connection.
3763    *
3764    *  Parameters:
3765    *    (Strophe.Connection) connection - Target Connection.
3766    */
3767   onStart: function(connection)
3768   {
3769     this._connection = connection;
3770   },
3771
3772   /** PrivateFunction: onChallenge
3773    *  Called by protocol implementation on incoming challenge. If client is
3774    *  first (isClientFirst == true) challenge will be null on the first call.
3775    *
3776    *  Parameters:
3777    *    (Strophe.Connection) connection - Target Connection.
3778    *    (String) challenge - current challenge to handle.
3779    *
3780    *  Returns:
3781    *    (String) Mechanism response.
3782    */
3783   /* jshint unused:false */
3784   onChallenge: function(connection, challenge) {
3785     throw new Error("You should implement challenge handling!");
3786   },
3787   /* jshint unused:true */
3788
3789   /** PrivateFunction: onFailure
3790    *  Protocol informs mechanism implementation about SASL failure.
3791    */
3792   onFailure: function() {
3793     this._connection = null;
3794   },
3795
3796   /** PrivateFunction: onSuccess
3797    *  Protocol informs mechanism implementation about SASL success.
3798    */
3799   onSuccess: function() {
3800     this._connection = null;
3801   }
3802 };
3803
3804   /** Constants: SASL mechanisms
3805    *  Available authentication mechanisms
3806    *
3807    *  Strophe.SASLAnonymous - SASL Anonymous authentication.
3808    *  Strophe.SASLPlain - SASL Plain authentication.
3809    *  Strophe.SASLMD5 - SASL Digest-MD5 authentication
3810    *  Strophe.SASLSHA1 - SASL SCRAM-SHA1 authentication
3811    */
3812
3813 // Building SASL callbacks
3814
3815 /** PrivateConstructor: SASLAnonymous
3816  *  SASL Anonymous authentication.
3817  */
3818 Strophe.SASLAnonymous = function() {};
3819
3820 Strophe.SASLAnonymous.prototype = new Strophe.SASLMechanism("ANONYMOUS", false, 10);
3821
3822 Strophe.SASLAnonymous.test = function(connection) {
3823   return connection.authcid === null;
3824 };
3825
3826 Strophe.Connection.prototype.mechanisms[Strophe.SASLAnonymous.prototype.name] = Strophe.SASLAnonymous;
3827
3828 /** PrivateConstructor: SASLPlain
3829  *  SASL Plain authentication.
3830  */
3831 Strophe.SASLPlain = function() {};
3832
3833 Strophe.SASLPlain.prototype = new Strophe.SASLMechanism("PLAIN", true, 20);
3834
3835 Strophe.SASLPlain.test = function(connection) {
3836   return connection.authcid !== null;
3837 };
3838
3839 Strophe.SASLPlain.prototype.onChallenge = function(connection) {
3840   var auth_str = connection.authzid;
3841   auth_str = auth_str + "\u0000";
3842   auth_str = auth_str + connection.authcid;
3843   auth_str = auth_str + "\u0000";
3844   auth_str = auth_str + connection.pass;
3845   return auth_str;
3846 };
3847
3848 Strophe.Connection.prototype.mechanisms[Strophe.SASLPlain.prototype.name] = Strophe.SASLPlain;
3849
3850 /** PrivateConstructor: SASLSHA1
3851  *  SASL SCRAM SHA 1 authentication.
3852  */
3853 Strophe.SASLSHA1 = function() {};
3854
3855 /* TEST:
3856  * This is a simple example of a SCRAM-SHA-1 authentication exchange
3857  * when the client doesn‘t support channel bindings (username ‘user‘ and
3858  * password ‘pencil‘ are used):
3859  *
3860  * C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
3861  * S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,
3862  * i=4096
3863  * C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,
3864  * p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
3865  * S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
3866  *
3867  */
3868
3869 Strophe.SASLSHA1.prototype = new Strophe.SASLMechanism("SCRAM-SHA-1", true, 40);
3870
3871 Strophe.SASLSHA1.test = function(connection) {
3872   return connection.authcid !== null;
3873 };
3874
3875 Strophe.SASLSHA1.prototype.onChallenge = function(connection, challenge, test_cnonce) {
3876   var cnonce = test_cnonce || MD5.hexdigest(Math.random() * 1234567890);
3877
3878   var auth_str = "n=" + connection.authcid;
3879   auth_str += ",r=";
3880   auth_str += cnonce;
3881
3882   connection._sasl_data.cnonce = cnonce;
3883   connection._sasl_data["client-first-message-bare"] = auth_str;
3884
3885   auth_str = "n,," + auth_str;
3886
3887   this.onChallenge = function (connection, challenge)
3888   {
3889     var nonce, salt, iter, Hi, U, U_old, i, k;
3890     var clientKey, serverKey, clientSignature;
3891     var responseText = "c=biws,";
3892     var authMessage = connection._sasl_data["client-first-message-bare"] + "," +
3893       challenge + ",";
3894     var cnonce = connection._sasl_data.cnonce;
3895     var attribMatch = /([a-z]+)=([^,]+)(,|$)/;
3896
3897     while (challenge.match(attribMatch)) {
3898       var matches = challenge.match(attribMatch);
3899       challenge = challenge.replace(matches[0], "");
3900       switch (matches[1]) {
3901       case "r":
3902         nonce = matches[2];
3903         break;
3904       case "s":
3905         salt = matches[2];
3906         break;
3907       case "i":
3908         iter = matches[2];
3909         break;
3910       }
3911     }
3912
3913     if (nonce.substr(0, cnonce.length) !== cnonce) {
3914       connection._sasl_data = {};
3915       return connection._sasl_failure_cb();
3916     }
3917
3918     responseText += "r=" + nonce;
3919     authMessage += responseText;
3920
3921     salt = Base64.decode(salt);
3922     salt += "\x00\x00\x00\x01";
3923
3924     Hi = U_old = SHA1.core_hmac_sha1(connection.pass, salt);
3925     for (i = 1; i < iter; i++) {
3926       U = SHA1.core_hmac_sha1(connection.pass, SHA1.binb2str(U_old));
3927       for (k = 0; k < 5; k++) {
3928         Hi[k] ^= U[k];
3929       }
3930       U_old = U;
3931     }
3932     Hi = SHA1.binb2str(Hi);
3933
3934     clientKey = SHA1.core_hmac_sha1(Hi, "Client Key");
3935     serverKey = SHA1.str_hmac_sha1(Hi, "Server Key");
3936     clientSignature = SHA1.core_hmac_sha1(SHA1.str_sha1(SHA1.binb2str(clientKey)), authMessage);
3937     connection._sasl_data["server-signature"] = SHA1.b64_hmac_sha1(serverKey, authMessage);
3938
3939     for (k = 0; k < 5; k++) {
3940       clientKey[k] ^= clientSignature[k];
3941     }
3942
3943     responseText += ",p=" + Base64.encode(SHA1.binb2str(clientKey));
3944
3945     return responseText;
3946   }.bind(this);
3947
3948   return auth_str;
3949 };
3950
3951 Strophe.Connection.prototype.mechanisms[Strophe.SASLSHA1.prototype.name] = Strophe.SASLSHA1;
3952
3953 /** PrivateConstructor: SASLMD5
3954  *  SASL DIGEST MD5 authentication.
3955  */
3956 Strophe.SASLMD5 = function() {};
3957
3958 Strophe.SASLMD5.prototype = new Strophe.SASLMechanism("DIGEST-MD5", false, 30);
3959
3960 Strophe.SASLMD5.test = function(connection) {
3961   return connection.authcid !== null;
3962 };
3963
3964 /** PrivateFunction: _quote
3965  *  _Private_ utility function to backslash escape and quote strings.
3966  *
3967  *  Parameters:
3968  *    (String) str - The string to be quoted.
3969  *
3970  *  Returns:
3971  *    quoted string
3972  */
3973 Strophe.SASLMD5.prototype._quote = function (str)
3974   {
3975     return ‘"‘ + str.replace(/\\/g, "\\\\").replace(/"/g, ‘\\"‘) + ‘"‘;
3976     //" end string workaround for emacs
3977   };
3978
3979
3980 Strophe.SASLMD5.prototype.onChallenge = function(connection, challenge, test_cnonce) {
3981   var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;
3982   var cnonce = test_cnonce || MD5.hexdigest("" + (Math.random() * 1234567890));
3983   var realm = "";
3984   var host = null;
3985   var nonce = "";
3986   var qop = "";
3987   var matches;
3988
3989   while (challenge.match(attribMatch)) {
3990     matches = challenge.match(attribMatch);
3991     challenge = challenge.replace(matches[0], "");
3992     matches[2] = matches[2].replace(/^"(.+)"$/, "$1");
3993     switch (matches[1]) {
3994     case "realm":
3995       realm = matches[2];
3996       break;
3997     case "nonce":
3998       nonce = matches[2];
3999       break;
4000     case "qop":
4001       qop = matches[2];
4002       break;
4003     case "host":
4004       host = matches[2];
4005       break;
4006     }
4007   }
4008
4009   var digest_uri = connection.servtype + "/" + connection.domain;
4010   if (host !== null) {
4011     digest_uri = digest_uri + "/" + host;
4012   }
4013
4014   var A1 = MD5.hash(connection.authcid +
4015                     ":" + realm + ":" + this._connection.pass) +
4016     ":" + nonce + ":" + cnonce;
4017   var A2 = ‘AUTHENTICATE:‘ + digest_uri;
4018
4019   var responseText = "";
4020   responseText += ‘charset=utf-8,‘;
4021   responseText += ‘username=‘ +
4022     this._quote(connection.authcid) + ‘,‘;
4023   responseText += ‘realm=‘ + this._quote(realm) + ‘,‘;
4024   responseText += ‘nonce=‘ + this._quote(nonce) + ‘,‘;
4025   responseText += ‘nc=00000001,‘;
4026   responseText += ‘cnonce=‘ + this._quote(cnonce) + ‘,‘;
4027   responseText += ‘digest-uri=‘ + this._quote(digest_uri) + ‘,‘;
4028   responseText += ‘response=‘ + MD5.hexdigest(MD5.hexdigest(A1) + ":" +
4029                                               nonce + ":00000001:" +
4030                                               cnonce + ":auth:" +
4031                                               MD5.hexdigest(A2)) + ",";
4032   responseText += ‘qop=auth‘;
4033
4034   this.onChallenge = function ()
4035   {
4036       return "";
4037   }.bind(this);
4038
4039   return responseText;
4040 };
4041
4042 Strophe.Connection.prototype.mechanisms[Strophe.SASLMD5.prototype.name] = Strophe.SASLMD5;
4043
4044 return {
4045     Strophe:        Strophe,
4046     $build:         $build,
4047     $msg:           $msg,
4048     $iq:            $iq,
4049     $pres:          $pres,
4050     SHA1:           SHA1,
4051     Base64:         Base64,
4052     MD5:            MD5,
4053 };
4054 }));
4055
4056 /*
4057     This program is distributed under the terms of the MIT license.
4058     Please see the LICENSE file for details.
4059
4060     Copyright 2006-2008, OGG, LLC
4061 */
4062
4063 /* jshint undef: true, unused: true:, noarg: true, latedef: true */
4064 /* global define, window, setTimeout, clearTimeout, XMLHttpRequest, ActiveXObject, Strophe, $build */
4065
4066 (function (root, factory) {
4067     if (typeof define === ‘function‘ && define.amd) {
4068         define(‘strophe-bosh‘, [‘strophe-core‘], function (core) {
4069             return factory(
4070                 core.Strophe,
4071                 core.$build
4072             );
4073         });
4074     } else {
4075         // Browser globals
4076         return factory(Strophe, $build);
4077     }
4078 }(this, function (Strophe, $build) {
4079
4080 /** PrivateClass: Strophe.Request
4081  *  _Private_ helper class that provides a cross implementation abstraction
4082  *  for a BOSH related XMLHttpRequest.
4083  *
4084  *  The Strophe.Request class is used internally to encapsulate BOSH request
4085  *  information.  It is not meant to be used from user‘s code.
4086  */
4087
4088 /** PrivateConstructor: Strophe.Request
4089  *  Create and initialize a new Strophe.Request object.
4090  *
4091  *  Parameters:
4092  *    (XMLElement) elem - The XML data to be sent in the request.
4093  *    (Function) func - The function that will be called when the
4094  *      XMLHttpRequest readyState changes.
4095  *    (Integer) rid - The BOSH rid attribute associated with this request.
4096  *    (Integer) sends - The number of times this same request has been
4097  *      sent.
4098  */
4099 Strophe.Request = function (elem, func, rid, sends)
4100 {
4101     this.id = ++Strophe._requestId;
4102     this.xmlData = elem;
4103     this.data = Strophe.serialize(elem);
4104     // save original function in case we need to make a new request
4105     // from this one.
4106     this.origFunc = func;
4107     this.func = func;
4108     this.rid = rid;
4109     this.date = NaN;
4110     this.sends = sends || 0;
4111     this.abort = false;
4112     this.dead = null;
4113
4114     this.age = function () {
4115         if (!this.date) { return 0; }
4116         var now = new Date();
4117         return (now - this.date) / 1000;
4118     };
4119     this.timeDead = function () {
4120         if (!this.dead) { return 0; }
4121         var now = new Date();
4122         return (now - this.dead) / 1000;
4123     };
4124     this.xhr = this._newXHR();
4125 };
4126
4127 Strophe.Request.prototype = {
4128     /** PrivateFunction: getResponse
4129      *  Get a response from the underlying XMLHttpRequest.
4130      *
4131      *  This function attempts to get a response from the request and checks
4132      *  for errors.
4133      *
4134      *  Throws:
4135      *    "parsererror" - A parser error occured.
4136      *
4137      *  Returns:
4138      *    The DOM element tree of the response.
4139      */
4140     getResponse: function ()
4141     {
4142         var node = null;
4143         if (this.xhr.responseXML && this.xhr.responseXML.documentElement) {
4144             node = this.xhr.responseXML.documentElement;
4145             if (node.tagName == "parsererror") {
4146                 Strophe.error("invalid response received");
4147                 Strophe.error("responseText: " + this.xhr.responseText);
4148                 Strophe.error("responseXML: " +
4149                               Strophe.serialize(this.xhr.responseXML));
4150                 throw "parsererror";
4151             }
4152         } else if (this.xhr.responseText) {
4153             Strophe.error("invalid response received");
4154             Strophe.error("responseText: " + this.xhr.responseText);
4155             Strophe.error("responseXML: " +
4156                           Strophe.serialize(this.xhr.responseXML));
4157         }
4158
4159         return node;
4160     },
4161
4162     /** PrivateFunction: _newXHR
4163      *  _Private_ helper function to create XMLHttpRequests.
4164      *
4165      *  This function creates XMLHttpRequests across all implementations.
4166      *
4167      *  Returns:
4168      *    A new XMLHttpRequest.
4169      */
4170     _newXHR: function ()
4171     {
4172         var xhr = null;
4173         if (window.XMLHttpRequest) {
4174             xhr = new XMLHttpRequest();
4175             if (xhr.overrideMimeType) {
4176                 xhr.overrideMimeType("text/xml; charset=utf-8");
4177             }
4178         } else if (window.ActiveXObject) {
4179             xhr = new ActiveXObject("Microsoft.XMLHTTP");
4180         }
4181
4182         // use Function.bind() to prepend ourselves as an argument
4183         xhr.onreadystatechange = this.func.bind(null, this);
4184
4185         return xhr;
4186     }
4187 };
4188
4189 /** Class: Strophe.Bosh
4190  *  _Private_ helper class that handles BOSH Connections
4191  *
4192  *  The Strophe.Bosh class is used internally by Strophe.Connection
4193  *  to encapsulate BOSH sessions. It is not meant to be used from user‘s code.
4194  */
4195
4196 /** File: bosh.js
4197  *  A JavaScript library to enable BOSH in Strophejs.
4198  *
4199  *  this library uses Bidirectional-streams Over Synchronous HTTP (BOSH)
4200  *  to emulate a persistent, stateful, two-way connection to an XMPP server.
4201  *  More information on BOSH can be found in XEP 124.
4202  */
4203
4204 /** PrivateConstructor: Strophe.Bosh
4205  *  Create and initialize a Strophe.Bosh object.
4206  *
4207  *  Parameters:
4208  *    (Strophe.Connection) connection - The Strophe.Connection that will use BOSH.
4209  *
4210  *  Returns:
4211  *    A new Strophe.Bosh object.
4212  */
4213 Strophe.Bosh = function(connection) {
4214     this._conn = connection;
4215     /* request id for body tags */
4216     this.rid = Math.floor(Math.random() * 4294967295);
4217     /* The current session ID. */
4218     this.sid = null;
4219
4220     // default BOSH values
4221     this.hold = 1;
4222     this.wait = 60;
4223     this.window = 5;
4224     this.errors = 0;
4225
4226     this._requests = [];
4227 };
4228
4229 Strophe.Bosh.prototype = {
4230     /** Variable: strip
4231      *
4232      *  BOSH-Connections will have all stanzas wrapped in a <body> tag when
4233      *  passed to <Strophe.Connection.xmlInput> or <Strophe.Connection.xmlOutput>.
4234      *  To strip this tag, User code can set <Strophe.Bosh.strip> to "body":
4235      *
4236      *  > Strophe.Bosh.prototype.strip = "body";
4237      *
4238      *  This will enable stripping of the body tag in both
4239      *  <Strophe.Connection.xmlInput> and <Strophe.Connection.xmlOutput>.
4240      */
4241     strip: null,
4242
4243     /** PrivateFunction: _buildBody
4244      *  _Private_ helper function to generate the <body/> wrapper for BOSH.
4245      *
4246      *  Returns:
4247      *    A Strophe.Builder with a <body/> element.
4248      */
4249     _buildBody: function ()
4250     {
4251         var bodyWrap = $build(‘body‘, {
4252             rid: this.rid++,
4253             xmlns: Strophe.NS.HTTPBIND
4254         });
4255         if (this.sid !== null) {
4256             bodyWrap.attrs({sid: this.sid});
4257         }
4258         if (this._conn.options.keepalive) {
4259             this._cacheSession();
4260         }
4261         return bodyWrap;
4262     },
4263
4264     /** PrivateFunction: _reset
4265      *  Reset the connection.
4266      *
4267      *  This function is called by the reset function of the Strophe Connection
4268      */
4269     _reset: function ()
4270     {
4271         this.rid = Math.floor(Math.random() * 4294967295);
4272         this.sid = null;
4273         this.errors = 0;
4274         window.sessionStorage.removeItem(‘strophe-bosh-session‘);
4275     },
4276
4277     /** PrivateFunction: _connect
4278      *  _Private_ function that initializes the BOSH connection.
4279      *
4280      *  Creates and sends the Request that initializes the BOSH connection.
4281      */
4282     _connect: function (wait, hold, route)
4283     {
4284         this.wait = wait || this.wait;
4285         this.hold = hold || this.hold;
4286         this.errors = 0;
4287
4288         // build the body tag
4289         var body = this._buildBody().attrs({
4290             to: this._conn.domain,
4291             "xml:lang": "en",
4292             wait: this.wait,
4293             hold: this.hold,
4294             content: "text/xml; charset=utf-8",
4295             ver: "1.6",
4296             "xmpp:version": "1.0",
4297             "xmlns:xmpp": Strophe.NS.BOSH
4298         });
4299
4300         if(route){
4301             body.attrs({
4302                 route: route
4303             });
4304         }
4305
4306         var _connect_cb = this._conn._connect_cb;
4307
4308         this._requests.push(
4309             new Strophe.Request(body.tree(),
4310                                 this._onRequestStateChange.bind(
4311                                     this, _connect_cb.bind(this._conn)),
4312                                 body.tree().getAttribute("rid")));
4313         this._throttledRequestHandler();
4314     },
4315
4316     /** PrivateFunction: _attach
4317      *  Attach to an already created and authenticated BOSH session.
4318      *
4319      *  This function is provided to allow Strophe to attach to BOSH
4320      *  sessions which have been created externally, perhaps by a Web
4321      *  application.  This is often used to support auto-login type features
4322      *  without putting user credentials into the page.
4323      *
4324      *  Parameters:
4325      *    (String) jid - The full JID that is bound by the session.
4326      *    (String) sid - The SID of the BOSH session.
4327      *    (String) rid - The current RID of the BOSH session.  This RID
4328      *      will be used by the next request.
4329      *    (Function) callback The connect callback function.
4330      *    (Integer) wait - The optional HTTPBIND wait value.  This is the
4331      *      time the server will wait before returning an empty result for
4332      *      a request.  The default setting of 60 seconds is recommended.
4333      *      Other settings will require tweaks to the Strophe.TIMEOUT value.
4334      *    (Integer) hold - The optional HTTPBIND hold value.  This is the
4335      *      number of connections the server will hold at one time.  This
4336      *      should almost always be set to 1 (the default).
4337      *    (Integer) wind - The optional HTTBIND window value.  This is the
4338      *      allowed range of request ids that are valid.  The default is 5.
4339      */
4340     _attach: function (jid, sid, rid, callback, wait, hold, wind)
4341     {
4342         this._conn.jid = jid;
4343         this.sid = sid;
4344         this.rid = rid;
4345
4346         this._conn.connect_callback = callback;
4347
4348         this._conn.domain = Strophe.getDomainFromJid(this._conn.jid);
4349
4350         this._conn.authenticated = true;
4351         this._conn.connected = true;
4352
4353         this.wait = wait || this.wait;
4354         this.hold = hold || this.hold;
4355         this.window = wind || this.window;
4356
4357         this._conn._changeConnectStatus(Strophe.Status.ATTACHED, null);
4358     },
4359
4360     /** PrivateFunction: _restore
4361      *  Attempt to restore a cached BOSH session
4362      *
4363      *  Parameters:
4364      *    (String) jid - The full JID that is bound by the session.
4365      *      This parameter is optional but recommended, specifically in cases
4366      *      where prebinded BOSH sessions are used where it‘s important to know
4367      *      that the right session is being restored.
4368      *    (Function) callback The connect callback function.
4369      *    (Integer) wait - The optional HTTPBIND wait value.  This is the
4370      *      time the server will wait before returning an empty result for
4371      *      a request.  The default setting of 60 seconds is recommended.
4372      *      Other settings will require tweaks to the Strophe.TIMEOUT value.
4373      *    (Integer) hold - The optional HTTPBIND hold value.  This is the
4374      *      number of connections the server will hold at one time.  This
4375      *      should almost always be set to 1 (the default).
4376      *    (Integer) wind - The optional HTTBIND window value.  This is the
4377      *      allowed range of request ids that are valid.  The default is 5.
4378      */
4379     _restore: function (jid, callback, wait, hold, wind)
4380     {
4381         var session = JSON.parse(window.sessionStorage.getItem(‘strophe-bosh-session‘));
4382         if (typeof session !== "undefined" &&
4383                    session !== null &&
4384                    session.rid &&
4385                    session.sid &&
4386                    session.jid &&

4387                    (typeof jid === "undefined" || Strophe.getBareJidFromJid(session.jid) == Strophe.getBareJidFromJid(jid)))
4388         {
4389             this._conn.restored = true;
4390             this._attach(session.jid, session.sid, session.rid, callback, wait, hold, wind);
4391         } else {
4392             throw { name: "StropheSessionError", message: "_restore: no restoreable session." };
4393         }
4394     },
4395
4396     /** PrivateFunction: _cacheSession
4397      *  _Private_ handler for the beforeunload event.
4398      *
4399      *  This handler is used to process the Bosh-part of the initial request.
4400      *  Parameters:
4401      *    (Strophe.Request) bodyWrap - The received stanza.
4402      */
4403     _cacheSession: function ()
4404     {
4405         if (this._conn.authenticated) {
4406             if (this._conn.jid && this.rid && this.sid) {
4407                 window.sessionStorage.setItem(‘strophe-bosh-session‘, JSON.stringify({
4408                     ‘jid‘: this._conn.jid,
4409                     ‘rid‘: this.rid,
4410                     ‘sid‘: this.sid
4411                 }));
4412             }
4413         } else {
4414             window.sessionStorage.removeItem(‘strophe-bosh-session‘);
4415         }
4416     },
4417
4418     /** PrivateFunction: _connect_cb
4419      *  _Private_ handler for initial connection request.
4420      *
4421      *  This handler is used to process the Bosh-part of the initial request.
4422      *  Parameters:
4423      *    (Strophe.Request) bodyWrap - The received stanza.
4424      */
4425     _connect_cb: function (bodyWrap)
4426     {
4427         var typ = bodyWrap.getAttribute("type");
4428         var cond, conflict;
4429         if (typ !== null && typ == "terminate") {
4430             // an error occurred
4431             cond = bodyWrap.getAttribute("condition");
4432             Strophe.error("BOSH-Connection failed: " + cond);
4433             conflict = bodyWrap.getElementsByTagName("conflict");
4434             if (cond !== null) {
4435                 if (cond == "remote-stream-error" && conflict.length > 0) {
4436                     cond = "conflict";
4437                 }
4438                 this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, cond);
4439             } else {
4440                 this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown");
4441             }
4442             this._conn._doDisconnect(cond);
4443             return Strophe.Status.CONNFAIL;
4444         }
4445
4446         // check to make sure we don‘t overwrite these if _connect_cb is
4447         // called multiple times in the case of missing stream:features
4448         if (!this.sid) {
4449             this.sid = bodyWrap.getAttribute("sid");
4450         }
4451         var wind = bodyWrap.getAttribute(‘requests‘);
4452         if (wind) { this.window = parseInt(wind, 10); }
4453         var hold = bodyWrap.getAttribute(‘hold‘);
4454         if (hold) { this.hold = parseInt(hold, 10); }
4455         var wait = bodyWrap.getAttribute(‘wait‘);
4456         if (wait) { this.wait = parseInt(wait, 10); }
4457     },
4458
4459     /** PrivateFunction: _disconnect
4460      *  _Private_ part of Connection.disconnect for Bosh
4461      *
4462      *  Parameters:
4463      *    (Request) pres - This stanza will be sent before disconnecting.
4464      */
4465     _disconnect: function (pres)
4466     {
4467         this._sendTerminate(pres);
4468     },
4469
4470     /** PrivateFunction: _doDisconnect
4471      *  _Private_ function to disconnect.
4472      *
4473      *  Resets the SID and RID.
4474      */
4475     _doDisconnect: function ()
4476     {
4477         this.sid = null;
4478         this.rid = Math.floor(Math.random() * 4294967295);
4479         window.sessionStorage.removeItem(‘strophe-bosh-session‘);
4480     },
4481
4482     /** PrivateFunction: _emptyQueue
4483      * _Private_ function to check if the Request queue is empty.
4484      *
4485      *  Returns:
4486      *    True, if there are no Requests queued, False otherwise.
4487      */
4488     _emptyQueue: function ()
4489     {
4490         return this._requests.length === 0;
4491     },
4492
4493     /** PrivateFunction: _hitError
4494      *  _Private_ function to handle the error count.
4495      *
4496      *  Requests are resent automatically until their error count reaches
4497      *  5.  Each time an error is encountered, this function is called to
4498      *  increment the count and disconnect if the count is too high.
4499      *
4500      *  Parameters:
4501      *    (Integer) reqStatus - The request status.
4502      */
4503     _hitError: function (reqStatus)
4504     {
4505         this.errors++;
4506         Strophe.warn("request errored, status: " + reqStatus +
4507                      ", number of errors: " + this.errors);
4508         if (this.errors > 4) {
4509             this._conn._onDisconnectTimeout();
4510         }
4511     },
4512
4513     /** PrivateFunction: _no_auth_received
4514      *
4515      * Called on stream start/restart when no stream:features
4516      * has been received and sends a blank poll request.
4517      */
4518     _no_auth_received: function (_callback)
4519     {
4520         if (_callback) {
4521             _callback = _callback.bind(this._conn);
4522         } else {
4523             _callback = this._conn._connect_cb.bind(this._conn);
4524         }
4525         var body = this._buildBody();
4526         this._requests.push(
4527                 new Strophe.Request(body.tree(),
4528                     this._onRequestStateChange.bind(
4529                         this, _callback.bind(this._conn)),
4530                     body.tree().getAttribute("rid")));
4531         this._throttledRequestHandler();
4532     },
4533
4534     /** PrivateFunction: _onDisconnectTimeout
4535      *  _Private_ timeout handler for handling non-graceful disconnection.
4536      *
4537      *  Cancels all remaining Requests and clears the queue.
4538      */
4539     _onDisconnectTimeout: function () {
4540         this._abortAllRequests();
4541     },
4542
4543     /** PrivateFunction: _abortAllRequests
4544      *  _Private_ helper function that makes sure all pending requests are aborted.
4545      */
4546     _abortAllRequests: function _abortAllRequests() {
4547         var req;
4548         while (this._requests.length > 0) {
4549             req = this._requests.pop();
4550             req.abort = true;
4551             req.xhr.abort();
4552             // jslint complains, but this is fine. setting to empty func
4553             // is necessary for IE6
4554             req.xhr.onreadystatechange = function () {}; // jshint ignore:line
4555         }
4556     },
4557
4558     /** PrivateFunction: _onIdle
4559      *  _Private_ handler called by Strophe.Connection._onIdle
4560      *
4561      *  Sends all queued Requests or polls with empty Request if there are none.
4562      */
4563     _onIdle: function () {
4564         var data = this._conn._data;
4565
4566         // if no requests are in progress, poll
4567         if (this._conn.authenticated && this._requests.length === 0 &&
4568             data.length === 0 && !this._conn.disconnecting) {
4569             Strophe.info("no requests during idle cycle, sending " +
4570                          "blank request");

4571             data.push(null);
4572         }
4573
4574         if (this._conn.paused) {
4575             return;
4576         }
4577
4578         if (this._requests.length < 2 && data.length > 0) {
4579             var body = this._buildBody();
4580             for (var i = 0; i < data.length; i++) {
4581                 if (data[i] !== null) {
4582                     if (data[i] === "restart") {
4583                         body.attrs({
4584                             to: this._conn.domain,
4585                             "xml:lang": "en",
4586                             "xmpp:restart": "true",
4587                             "xmlns:xmpp": Strophe.NS.BOSH
4588                         });
4589                     } else {
4590                         body.cnode(data[i]).up();
4591                     }
4592                 }
4593             }
4594             delete this._conn._data;
4595             this._conn._data = [];
4596             this._requests.push(
4597                 new Strophe.Request(body.tree(),
4598                                     this._onRequestStateChange.bind(
4599                                         this, this._conn._dataRecv.bind(this._conn)),
4600                                     body.tree().getAttribute("rid")));
4601             this._throttledRequestHandler();
4602         }
4603
4604         if (this._requests.length > 0) {
4605             var time_elapsed = this._requests[0].age();
4606             if (this._requests[0].dead !== null) {
4607                 if (this._requests[0].timeDead() >
4608                     Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) {
4609                     this._throttledRequestHandler();
4610                 }
4611             }
4612
4613             if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) {
4614                 Strophe.warn("Request " +
4615                              this._requests[0].id +
4616                              " timed out, over " + Math.floor(Strophe.TIMEOUT * this.wait) +
4617                              " seconds since last activity");
4618                 this._throttledRequestHandler();
4619             }
4620         }
4621     },
4622
4623     /** PrivateFunction: _onRequestStateChange
4624      *  _Private_ handler for Strophe.Request state changes.
4625      *
4626      *  This function is called when the XMLHttpRequest readyState changes.
4627      *  It contains a lot of error handling logic for the many ways that
4628      *  requests can fail, and calls the request callback when requests
4629      *  succeed.
4630      *
4631      *  Parameters:
4632      *    (Function) func - The handler for the request.
4633      *    (Strophe.Request) req - The request that is changing readyState.
4634      */
4635     _onRequestStateChange: function (func, req)
4636     {
4637         Strophe.debug("request id " + req.id +
4638                       "." + req.sends + " state changed to " +
4639                       req.xhr.readyState);
4640
4641         if (req.abort) {
4642             req.abort = false;
4643             return;
4644         }
4645
4646         // request complete
4647         var reqStatus;
4648         if (req.xhr.readyState == 4) {
4649             reqStatus = 0;
4650             try {
4651                 reqStatus = req.xhr.status;
4652             } catch (e) {
4653                 // ignore errors from undefined status attribute.  works
4654                 // around a browser bug
4655             }
4656
4657             if (typeof(reqStatus) == "undefined") {
4658                 reqStatus = 0;
4659             }
4660
4661             if (this.disconnecting) {
4662                 if (reqStatus >= 400) {
4663                     this._hitError(reqStatus);
4664                     return;
4665                 }
4666             }
4667
4668             var reqIs0 = (this._requests[0] == req);
4669             var reqIs1 = (this._requests[1] == req);
4670
4671             if ((reqStatus > 0 && reqStatus < 500) || req.sends > 5) {
4672                 // remove from internal queue
4673                 this._removeRequest(req);
4674                 Strophe.debug("request id " +
4675                               req.id +
4676                               " should now be removed");
4677             }
4678
4679             // request succeeded
4680             if (reqStatus == 200) {
4681                 // if request 1 finished, or request 0 finished and request
4682                 // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to
4683                 // restart the other - both will be in the first spot, as the
4684                 // completed request has been removed from the queue already
4685                 if (reqIs1 ||
4686                     (reqIs0 && this._requests.length > 0 &&
4687                      this._requests[0].age() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait))) {
4688                     this._restartRequest(0);
4689                 }
4690                 // call handler
4691                 Strophe.debug("request id " +
4692                               req.id + "." +
4693                               req.sends + " got 200");
4694                 func(req);
4695                 this.errors = 0;
4696             } else {
4697                 Strophe.error("request id " +
4698                               req.id + "." +
4699                               req.sends + " error " + reqStatus +
4700                               " happened");
4701                 if (reqStatus === 0 ||
4702                     (reqStatus >= 400 && reqStatus < 600) ||
4703                     reqStatus >= 12000) {
4704                     this._hitError(reqStatus);
4705                     if (reqStatus >= 400 && reqStatus < 500) {
4706                         this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING, null);
4707                         this._conn._doDisconnect();
4708                     }
4709                 }
4710             }
4711
4712             if (!((reqStatus > 0 && reqStatus < 500) ||
4713                   req.sends > 5)) {
4714                 this._throttledRequestHandler();
4715             }
4716         }
4717     },
4718
4719     /** PrivateFunction: _processRequest
4720      *  _Private_ function to process a request in the queue.
4721      *
4722      *  This function takes requests off the queue and sends them and
4723      *  restarts dead requests.
4724      *
4725      *  Parameters:
4726      *    (Integer) i - The index of the request in the queue.
4727      */
4728     _processRequest: function (i)
4729     {
4730         var self = this;
4731         var req = this._requests[i];
4732         var reqStatus = -1;
4733
4734         try {
4735             if (req.xhr.readyState == 4) {
4736                 reqStatus = req.xhr.status;
4737             }
4738         } catch (e) {
4739             Strophe.error("caught an error in _requests[" + i +
4740                           "], reqStatus: " + reqStatus);
4741         }
4742
4743         if (typeof(reqStatus) == "undefined") {
4744             reqStatus = -1;
4745         }
4746
4747         // make sure we limit the number of retries
4748         if (req.sends > this._conn.maxRetries) {
4749             this._conn._onDisconnectTimeout();
4750             return;
4751         }
4752
4753         var time_elapsed = req.age();
4754         var primaryTimeout = (!isNaN(time_elapsed) &&
4755                               time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait));
4756         var secondaryTimeout = (req.dead !== null &&
4757                                 req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait));
4758         var requestCompletedWithServerError = (req.xhr.readyState == 4 &&
4759                                                (reqStatus < 1 ||
4760                                                 reqStatus >= 500));
4761         if (primaryTimeout || secondaryTimeout ||
4762             requestCompletedWithServerError) {
4763             if (secondaryTimeout) {
4764                 Strophe.error("Request " +
4765                               this._requests[i].id +
4766                               " timed out (secondary), restarting");
4767             }
4768             req.abort = true;
4769             req.xhr.abort();
4770             // setting to null fails on IE6, so set to empty function
4771             req.xhr.onreadystatechange = function () {};
4772             this._requests[i] = new Strophe.Request(req.xmlData,
4773                                                     req.origFunc,
4774                                                     req.rid,
4775                                                     req.sends);
4776             req = this._requests[i];
4777         }
4778
4779         if (req.xhr.readyState === 0) {
4780             Strophe.debug("request id " + req.id +
4781                           "." + req.sends + " posting");
4782
4783             try {
4784                 req.xhr.open("POST", this._conn.service, this._conn.options.sync ? false : true);
4785                 req.xhr.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
4786             } catch (e2) {
4787                 Strophe.error("XHR open failed.");
4788                 if (!this._conn.connected) {
4789                     this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,
4790                                               "bad-service");
4791                 }
4792                 this._conn.disconnect();
4793                 return;
4794             }
4795
4796             // Fires the XHR request -- may be invoked immediately
4797             // or on a gradually expanding retry window for reconnects
4798             var sendFunc = function () {
4799                 req.date = new Date();
4800                 if (self._conn.options.customHeaders){
4801                     var headers = self._conn.options.customHeaders;
4802                     for (var header in headers) {
4803                         if (headers.hasOwnProperty(header)) {
4804                             req.xhr.setRequestHeader(header, headers[header]);
4805                         }
4806                     }
4807                 }
4808                 req.xhr.send(req.data);
4809             };
4810
4811             // Implement progressive backoff for reconnects --
4812             // First retry (send == 1) should also be instantaneous
4813             if (req.sends > 1) {
4814                 // Using a cube of the retry number creates a nicely
4815                 // expanding retry window
4816                 var backoff = Math.min(Math.floor(Strophe.TIMEOUT * this.wait),
4817                                        Math.pow(req.sends, 3)) * 1000;
4818                 setTimeout(sendFunc, backoff);
4819             } else {
4820                 sendFunc();
4821             }
4822
4823             req.sends++;
4824
4825             if (this._conn.xmlOutput !== Strophe.Connection.prototype.xmlOutput) {
4826                 if (req.xmlData.nodeName === this.strip && req.xmlData.childNodes.length) {
4827                     this._conn.xmlOutput(req.xmlData.childNodes[0]);
4828                 } else {
4829                     this._conn.xmlOutput(req.xmlData);
4830                 }
4831             }
4832             if (this._conn.rawOutput !== Strophe.Connection.prototype.rawOutput) {
4833                 this._conn.rawOutput(req.data);
4834             }
4835         } else {
4836             Strophe.debug("_processRequest: " +
4837                           (i === 0 ? "first" : "second") +
4838                           " request has readyState of " +
4839                           req.xhr.readyState);
4840         }
4841     },
4842
4843     /** PrivateFunction: _removeRequest
4844      *  _Private_ function to remove a request from the queue.
4845      *
4846      *  Parameters:
4847      *    (Strophe.Request) req - The request to remove.
4848      */
4849     _removeRequest: function (req)
4850     {
4851         Strophe.debug("removing request");
4852
4853         var i;
4854         for (i = this._requests.length - 1; i >= 0; i--) {
4855             if (req == this._requests[i]) {
4856                 this._requests.splice(i, 1);
4857             }
4858         }
4859
4860         // IE6 fails on setting to null, so set to empty function
4861         req.xhr.onreadystatechange = function () {};
4862
4863         this._throttledRequestHandler();
4864     },
4865
4866     /** PrivateFunction: _restartRequest
4867      *  _Private_ function to restart a request that is presumed dead.
4868      *
4869      *  Parameters:
4870      *    (Integer) i - The index of the request in the queue.
4871      */
4872     _restartRequest: function (i)
4873     {
4874         var req = this._requests[i];
4875         if (req.dead === null) {
4876             req.dead = new Date();
4877         }
4878
4879         this._processRequest(i);
4880     },
4881
4882     /** PrivateFunction: _reqToData
4883      * _Private_ function to get a stanza out of a request.
4884      *
4885      * Tries to extract a stanza out of a Request Object.
4886      * When this fails the current connection will be disconnected.
4887      *
4888      *  Parameters:
4889      *    (Object) req - The Request.
4890      *
4891      *  Returns:
4892      *    The stanza that was passed.
4893      */
4894     _reqToData: function (req)
4895     {
4896         try {
4897             return req.getResponse();
4898         } catch (e) {
4899             if (e != "parsererror") { throw e; }
4900             this._conn.disconnect("strophe-parsererror");
4901         }
4902     },
4903
4904     /** PrivateFunction: _sendTerminate
4905      *  _Private_ function to send initial disconnect sequence.
4906      *
4907      *  This is the first step in a graceful disconnect.  It sends
4908      *  the BOSH server a terminate body and includes an unavailable
4909      *  presence if authentication has completed.
4910      */
4911     _sendTerminate: function (pres)
4912     {
4913         Strophe.info("_sendTerminate was called");
4914         var body = this._buildBody().attrs({type: "terminate"});
4915
4916         if (pres) {
4917             body.cnode(pres.tree());
4918         }
4919
4920         var req = new Strophe.Request(body.tree(),
4921                                       this._onRequestStateChange.bind(
4922                                           this, this._conn._dataRecv.bind(this._conn)),
4923                                       body.tree().getAttribute("rid"));
4924
4925         this._requests.push(req);
4926         this._throttledRequestHandler();
4927     },
4928
4929     /** PrivateFunction: _send
4930      *  _Private_ part of the Connection.send function for BOSH
4931      *
4932      * Just triggers the RequestHandler to send the messages that are in the queue
4933      */
4934     _send: function () {
4935         clearTimeout(this._conn._idleTimeout);
4936         this._throttledRequestHandler();
4937         this._conn._idleTimeout = setTimeout(this._conn._onIdle.bind(this._conn), 100);
4938     },
4939
4940     /** PrivateFunction: _sendRestart
4941      *
4942      *  Send an xmpp:restart stanza.
4943      */
4944     _sendRestart: function ()
4945     {
4946         this._throttledRequestHandler();
4947         clearTimeout(this._conn._idleTimeout);
4948     },
4949
4950     /** PrivateFunction: _throttledRequestHandler
4951      *  _Private_ function to throttle requests to the connection window.
4952      *
4953      *  This function makes sure we don‘t send requests so fast that the
4954      *  request ids overflow the connection window in the case that one
4955      *  request died.
4956      */
4957     _throttledRequestHandler: function ()
4958     {
4959         if (!this._requests) {
4960             Strophe.debug("_throttledRequestHandler called with " +
4961                           "undefined requests");
4962         } else {
4963             Strophe.debug("_throttledRequestHandler called with " +
4964                           this._requests.length + " requests");
4965         }
4966
4967         if (!this._requests || this._requests.length === 0) {
4968             return;
4969         }
4970
4971         if (this._requests.length > 0) {
4972             this._processRequest(0);
4973         }
4974
4975         if (this._requests.length > 1 &&
4976             Math.abs(this._requests[0].rid -
4977                      this._requests[1].rid) < this.window) {
4978             this._processRequest(1);
4979         }
4980     }
4981 };
4982 return Strophe;
4983 }));
4984
4985 /*
4986     This program is distributed under the terms of the MIT license.
4987     Please see the LICENSE file for details.
4988
4989     Copyright 2006-2008, OGG, LLC
4990 */
4991
4992 /* jshint undef: true, unused: true:, noarg: true, latedef: true */
4993 /* global define, window, clearTimeout, WebSocket, DOMParser, Strophe, $build */
4994
4995 (function (root, factory) {
4996     if (typeof define === ‘function‘ && define.amd) {
4997         define(‘strophe-websocket‘, [‘strophe-core‘], function (core) {
4998             return factory(
4999                 core.Strophe,
5000                 core.$build
5001             );
5002         });
5003     } else {
5004         // Browser globals
5005         return factory(Strophe, $build);
5006     }
5007 }(this, function (Strophe, $build) {
5008
5009 /** Class: Strophe.WebSocket
5010  *  _Private_ helper class that handles WebSocket Connections
5011  *
5012  *  The Strophe.WebSocket class is used internally by Strophe.Connection
5013  *  to encapsulate WebSocket sessions. It is not meant to be used from user‘s code.
5014  */
5015
5016 /** File: websocket.js
5017  *  A JavaScript library to enable XMPP over Websocket in Strophejs.
5018  *
5019  *  This file implements XMPP over WebSockets for Strophejs.
5020  *  If a Connection is established with a Websocket url (ws://...)
5021  *  Strophe will use WebSockets.
5022  *  For more information on XMPP-over-WebSocket see RFC 7395:
5023  *  http://tools.ietf.org/html/rfc7395
5024  *
5025  *  WebSocket support implemented by Andreas Guth ([email protected])
5026  */
5027
5028 /** PrivateConstructor: Strophe.Websocket
5029  *  Create and initialize a Strophe.WebSocket object.
5030  *  Currently only sets the connection Object.
5031  *
5032  *  Parameters:
5033  *    (Strophe.Connection) connection - The Strophe.Connection that will use WebSockets.
5034  *
5035  *  Returns:
5036  *    A new Strophe.WebSocket object.
5037  */
5038 Strophe.Websocket = function(connection) {
5039     alert(window.location.host);
5040     this._conn = connection;
5041     this.strip = "wrapper";
5042
5043     var service = connection.service;
5044     if (service.indexOf("ws:") !== 0 && service.indexOf("wss:") !== 0) {
5045         // If the service is not an absolute URL, assume it is a path and put the absolute
5046         // URL together from options, current URL and the path.
5047         var new_service = "";
5048
5049         if (connection.options.protocol === "ws" && window.location.protocol !== "https:") {
5050             new_service += "ws";
5051         } else {
5052             new_service += "wss";
5053         }
5054
5055         new_service += "://" + window.location.host;
5056
5057         if (service.indexOf("/") !== 0) {
5058             new_service += window.location.pathname + service;
5059         } else {
5060             new_service += service;
5061         }
5062
5063         connection.service = new_service;
5064     }
5065 };
5066
5067 Strophe.Websocket.prototype = {
5068     /** PrivateFunction: _buildStream
5069      *  _Private_ helper function to generate the <stream> start tag for WebSockets
5070      *
5071      *  Returns:
5072      *    A Strophe.Builder with a <stream> element.
5073      */
5074     _buildStream: function ()
5075     {
5076         return $build("open", {
5077             "xmlns": Strophe.NS.FRAMING,
5078             "to": this._conn.domain,
5079             "version": ‘1.0‘
5080         });
5081     },
5082
5083     /** PrivateFunction: _check_streamerror
5084      * _Private_ checks a message for stream:error
5085      *
5086      *  Parameters:
5087      *    (Strophe.Request) bodyWrap - The received stanza.
5088      *    connectstatus - The ConnectStatus that will be set on error.
5089      *  Returns:
5090      *     true if there was a streamerror, false otherwise.
5091      */
5092     _check_streamerror: function (bodyWrap, connectstatus) {
5093         var errors;
5094         if (bodyWrap.getElementsByTagNameNS) {
5095             errors = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, "error");
5096         } else {
5097             errors = bodyWrap.getElementsByTagName("stream:error");
5098         }
5099         if (errors.length === 0) {
5100             return false;
5101         }
5102         var error = errors[0];
5103
5104         var condition = "";
5105         var text = "";
5106
5107         var ns = "urn:ietf:params:xml:ns:xmpp-streams";
5108         for (var i = 0; i < error.childNodes.length; i++) {
5109             var e = error.childNodes[i];
5110             if (e.getAttribute("xmlns") !== ns) {
5111                 break;
5112             } if (e.nodeName === "text") {
5113                 text = e.textContent;
5114             } else {
5115                 condition = e.nodeName;
5116             }
5117         }
5118
5119         var errorString = "WebSocket stream error: ";
5120
5121         if (condition) {
5122             errorString += condition;
5123         } else {
5124             errorString += "unknown";
5125         }
5126
5127         if (text) {
5128             errorString += " - " + condition;
5129         }
5130
5131         Strophe.error(errorString);
5132
5133         // close the connection on stream_error
5134         this._conn._changeConnectStatus(connectstatus, condition);
5135         this._conn._doDisconnect();
5136         return true;
5137     },
5138
5139     /** PrivateFunction: _reset
5140      *  Reset the connection.
5141      *
5142      *  This function is called by the reset function of the Strophe Connection.
5143      *  Is not needed by WebSockets.
5144      */
5145     _reset: function ()
5146     {
5147         return;
5148     },
5149
5150     /** PrivateFunction: _connect
5151      *  _Private_ function called by Strophe.Connection.connect
5152      *
5153      *  Creates a WebSocket for a connection and assigns Callbacks to it.
5154      *  Does nothing if there already is a WebSocket.
5155      */
5156     _connect: function () {
5157         // Ensure that there is no open WebSocket from a previous Connection.
5158         this._closeSocket();
5159
5160         // Create the new WobSocket
5161         this.socket = new WebSocket(this._conn.service, "xmpp");
5162         this.socket.onopen = this._onOpen.bind(this);
5163         this.socket.onerror = this._onError.bind(this);
5164         this.socket.onclose = this._onClose.bind(this);
5165         this.socket.onmessage = this._connect_cb_wrapper.bind(this);
5166     },
5167
5168     /** PrivateFunction: _connect_cb
5169      *  _Private_ function called by Strophe.Connection._connect_cb
5170      *
5171      * checks for stream:error
5172      *
5173      *  Parameters:
5174      *    (Strophe.Request) bodyWrap - The received stanza.
5175      */
5176     _connect_cb: function(bodyWrap) {
5177         var error = this._check_streamerror(bodyWrap, Strophe.Status.CONNFAIL);
5178         if (error) {
5179             return Strophe.Status.CONNFAIL;
5180         }
5181     },
5182
5183     /** PrivateFunction: _handleStreamStart
5184      * _Private_ function that checks the opening <open /> tag for errors.
5185      *
5186      * Disconnects if there is an error and returns false, true otherwise.
5187      *
5188      *  Parameters:
5189      *    (Node) message - Stanza containing the <open /> tag.
5190      */
5191     _handleStreamStart: function(message) {
5192         var error = false;
5193
5194         // Check for errors in the <open /> tag
5195         var ns = message.getAttribute("xmlns");
5196         if (typeof ns !== "string") {
5197             error = "Missing xmlns in <open />";
5198         } else if (ns !== Strophe.NS.FRAMING) {
5199             error = "Wrong xmlns in <open />: " + ns;
5200         }
5201
5202         var ver = message.getAttribute("version");
5203         if (typeof ver !== "string") {
5204             error = "Missing version in <open />";
5205         } else if (ver !== "1.0") {
5206             error = "Wrong version in <open />: " + ver;
5207         }
5208
5209         if (error) {
5210             this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, error);
5211             this._conn._doDisconnect();
5212             return false;
5213         }
5214
5215         return true;
5216     },
5217
5218     /** PrivateFunction: _connect_cb_wrapper
5219      * _Private_ function that handles the first connection messages.
5220      *
5221      * On receiving an opening stream tag this callback replaces itself with the real
5222      * message handler. On receiving a stream error the connection is terminated.
5223      */
5224     _connect_cb_wrapper: function(message) {
5225         if (message.data.indexOf("<open ") === 0 || message.data.indexOf("<?xml") === 0) {
5226             // Strip the XML Declaration, if there is one
5227             var data = message.data.replace(/^(<\?.*?\?>\s*)*/, "");
5228             if (data === ‘‘) return;
5229
5230             var streamStart = new DOMParser().parseFromString(data, "text/xml").documentElement;
5231             this._conn.xmlInput(streamStart);
5232             this._conn.rawInput(message.data);
5233
5234             //_handleStreamSteart will check for XML errors and disconnect on error
5235             if (this._handleStreamStart(streamStart)) {
5236                 //_connect_cb will check for stream:error and disconnect on error
5237                 this._connect_cb(streamStart);
5238             }
5239         } else if (message.data.indexOf("<close ") === 0) { //‘<close xmlns="urn:ietf:params:xml:ns:xmpp-framing />‘) {
5240             this._conn.rawInput(message.data);
5241             this._conn.xmlInput(message);
5242             var see_uri = message.getAttribute("see-other-uri");
5243             if (see_uri) {
5244                 this._conn._changeConnectStatus(Strophe.Status.REDIRECT, "Received see-other-uri, resetting connection");
5245                 this._conn.reset();
5246                 this._conn.service = see_uri;
5247                 this._connect();
5248             } else {
5249                 this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Received closing stream");
5250                 this._conn._doDisconnect();
5251             }
5252         } else {
5253             var string = this._streamWrap(message.data);
5254             var elem = new DOMParser().parseFromString(string, "text/xml").documentElement;
5255             this.socket.onmessage = this._onMessage.bind(this);
5256             this._conn._connect_cb(elem, null, message.data);
5257         }
5258     },
5259
5260     /** PrivateFunction: _disconnect
5261      *  _Private_ function called by Strophe.Connection.disconnect
5262      *
5263      *  Disconnects and sends a last stanza if one is given
5264      *
5265      *  Parameters:
5266      *    (Request) pres - This stanza will be sent before disconnecting.
5267      */
5268     _disconnect: function (pres)
5269     {
5270         if (this.socket && this.socket.readyState !== WebSocket.CLOSED) {
5271             if (pres) {
5272                 this._conn.send(pres);
5273             }
5274             var close = $build("close", { "xmlns": Strophe.NS.FRAMING, });
5275             this._conn.xmlOutput(close);
5276             var closeString = Strophe.serialize(close);
5277             this._conn.rawOutput(closeString);
5278             try {
5279                 this.socket.send(closeString);
5280             } catch (e) {
5281                 Strophe.info("Couldn‘t send <close /> tag.");
5282             }
5283         }
5284         this._conn._doDisconnect();
5285     },
5286
5287     /** PrivateFunction: _doDisconnect
5288      *  _Private_ function to disconnect.
5289      *
5290      *  Just closes the Socket for WebSockets
5291      */
5292     _doDisconnect: function ()
5293     {
5294         Strophe.info("WebSockets _doDisconnect was called");
5295         this._closeSocket();
5296     },
5297
5298     /** PrivateFunction _streamWrap
5299      *  _Private_ helper function to wrap a stanza in a <stream> tag.
5300      *  This is used so Strophe can process stanzas from WebSockets like BOSH
5301      */
5302     _streamWrap: function (stanza)
5303     {
5304         return "<wrapper>" + stanza + ‘</wrapper>‘;
5305     },
5306
5307
5308     /** PrivateFunction: _closeSocket
5309      *  _Private_ function to close the WebSocket.
5310      *
5311      *  Closes the socket if it is still open and deletes it
5312      */
5313     _closeSocket: function ()
5314     {
5315         if (this.socket) { try {
5316             this.socket.close();
5317         } catch (e) {} }
5318         this.socket = null;
5319     },
5320
5321     /** PrivateFunction: _emptyQueue
5322      * _Private_ function to check if the message queue is empty.
5323      *
5324      *  Returns:
5325      *    True, because WebSocket messages are send immediately after queueing.
5326      */
5327     _emptyQueue: function ()
5328     {
5329         return true;
5330     },
5331
5332     /** PrivateFunction: _onClose
5333      * _Private_ function to handle websockets closing.
5334      *
5335      * Nothing to do here for WebSockets
5336      */
5337     _onClose: function() {
5338         if(this._conn.connected && !this._conn.disconnecting) {
5339             Strophe.error("Websocket closed unexcectedly");
5340             this._conn._doDisconnect();
5341         } else {
5342             Strophe.info("Websocket closed");
5343         }
5344     },
5345
5346     /** PrivateFunction: _no_auth_received
5347      *
5348      * Called on stream start/restart when no stream:features
5349      * has been received.
5350      */
5351     _no_auth_received: function (_callback)
5352     {
5353         Strophe.error("Server did not send any auth methods");
5354         this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Server did not send any auth methods");
5355         if (_callback) {
5356             _callback = _callback.bind(this._conn);
5357             _callback();
5358         }
5359         this._conn._doDisconnect();
5360     },
5361
5362     /** PrivateFunction: _onDisconnectTimeout
5363      *  _Private_ timeout handler for handling non-graceful disconnection.
5364      *
5365      *  This does nothing for WebSockets
5366      */
5367     _onDisconnectTimeout: function () {},
5368
5369     /** PrivateFunction: _abortAllRequests
5370      *  _Private_ helper function that makes sure all pending requests are aborted.
5371      */
5372     _abortAllRequests: function () {},
5373
5374     /** PrivateFunction: _onError
5375      * _Private_ function to handle websockets errors.
5376      *
5377      * Parameters:
5378      * (Object) error - The websocket error.
5379      */
5380     _onError: function(error) {
5381         Strophe.error("Websocket error " + error);
5382         this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "The WebSocket connection could not be established was disconnected.");
5383         this._disconnect();
5384     },
5385
5386     /** PrivateFunction: _onIdle
5387      *  _Private_ function called by Strophe.Connection._onIdle
5388      *
5389      *  sends all queued stanzas
5390      */
5391     _onIdle: function () {
5392         var data = this._conn._data;
5393         if (data.length > 0 && !this._conn.paused) {
5394             for (var i = 0; i < data.length; i++) {
5395                 if (data[i] !== null) {
5396                     var stanza, rawStanza;
5397                     if (data[i] === "restart") {
5398                         stanza = this._buildStream().tree();
5399                     } else {
5400                         stanza = data[i];
5401                     }
5402                     rawStanza = Strophe.serialize(stanza);
5403                     this._conn.xmlOutput(stanza);
5404                     this._conn.rawOutput(rawStanza);
5405                     this.socket.send(rawStanza);
5406                 }
5407             }
5408             this._conn._data = [];
5409         }
5410     },
5411
5412     /** PrivateFunction: _onMessage
5413      * _Private_ function to handle websockets messages.
5414      *
5415      * This function parses each of the messages as if they are full documents. [TODO : We may actually want to use a SAX Push parser].
5416      *
5417      * Since all XMPP traffic starts with "<stream:stream version=‘1.0‘ xml:lang=‘en‘ xmlns=‘jabber:client‘ xmlns:stream=‘http://etherx.jabber.org/streams‘ id=‘3697395463‘ from=‘SERVER‘>"
5418      * The first stanza will always fail to be parsed...
5419      * Addtionnaly, the seconds stanza will always be a <stream:features> with the stream NS defined in the previous stanza... so we need to ‘force‘ the inclusion of the NS in this stanza!
5420      *
5421      * Parameters:
5422      * (string) message - The websocket message.
5423      */
5424     _onMessage: function(message) {
5425         var elem, data;
5426         // check for closing stream
5427         var close = ‘<close xmlns="urn:ietf:params:xml:ns:xmpp-framing" />‘;
5428         if (message.data === close) {
5429             this._conn.rawInput(close);
5430             this._conn.xmlInput(message);
5431             if (!this._conn.disconnecting) {
5432                 this._conn._doDisconnect();
5433             }
5434             return;
5435         } else if (message.data.search("<open ") === 0) {
5436             // This handles stream restarts
5437             elem = new DOMParser().parseFromString(message.data, "text/xml").documentElement;
5438
5439             if (!this._handleStreamStart(elem)) {
5440                 return;
5441             }
5442         } else {
5443             data = this._streamWrap(message.data);
5444             elem = new DOMParser().parseFromString(data, "text/xml").documentElement;
5445         }
5446
5447         if (this._check_streamerror(elem, Strophe.Status.ERROR)) {
5448             return;
5449         }
5450
5451         //handle unavailable presence stanza before disconnecting
5452         if (this._conn.disconnecting &&
5453                 elem.firstChild.nodeName === "presence" &&
5454                 elem.firstChild.getAttribute("type") === "unavailable") {
5455             this._conn.xmlInput(elem);
5456             this._conn.rawInput(Strophe.serialize(elem));
5457             // if we are already disconnecting we will ignore the unavailable stanza and
5458             // wait for the </stream:stream> tag before we close the connection
5459             return;
5460         }
5461         this._conn._dataRecv(elem, message.data);
5462     },
5463
5464     /** PrivateFunction: _onOpen
5465      * _Private_ function to handle websockets connection setup.
5466      *
5467      * The opening stream tag is sent here.
5468      */
5469     _onOpen: function() {
5470         Strophe.info("Websocket open");
5471         var start = this._buildStream();
5472         this._conn.xmlOutput(start.tree());
5473
5474         var startString = Strophe.serialize(start);
5475         this._conn.rawOutput(startString);
5476         this.socket.send(startString);
5477     },
5478
5479     /** PrivateFunction: _reqToData
5480      * _Private_ function to get a stanza out of a request.
5481      *
5482      * WebSockets don‘t use requests, so the passed argument is just returned.
5483      *
5484      *  Parameters:
5485      *    (Object) stanza - The stanza.
5486      *
5487      *  Returns:
5488      *    The stanza that was passed.
5489      */
5490     _reqToData: function (stanza)
5491     {
5492         return stanza;
5493     },
5494
5495     /** PrivateFunction: _send
5496      *  _Private_ part of the Connection.send function for WebSocket
5497      *
5498      * Just flushes the messages that are in the queue
5499      */
5500     _send: function () {
5501         this._conn.flush();
5502     },
5503
5504     /** PrivateFunction: _sendRestart
5505      *
5506      *  Send an xmpp:restart stanza.
5507      */
5508     _sendRestart: function ()
5509     {
5510         clearTimeout(this._conn._idleTimeout);
5511         this._conn._onIdle.bind(this._conn)();
5512     }
5513 };
5514 return Strophe;
5515 }));
5516
5517 /* jshint ignore:start */
5518 if (callback) {
5519     return callback(Strophe, $build, $msg, $iq, $pres);
5520 }
5521
5522
5523 })(function (Strophe, build, msg, iq, pres) {
5524     window.Strophe = Strophe;
5525     window.$build = build;
5526     window.$msg = msg;
5527     window.$iq = iq;
5528     window.$pres = pres;
5529 });
5530 /* jshint ignore:end */
5531
5532
5533
5534 /*utf*/
5535 function utf16to8(str) {
5536     var out, i, len, c;
5537     out = "";
5538     len = str.length;
5539     for(i = 0; i < len; i++) {
5540     c = str.charCodeAt(i);
5541     if ((c >= 0x0001) && (c <= 0x007F)) {
5542     out += str.charAt(i);
5543     } else if (c > 0x07FF) {
5544     out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
5545     out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
5546     out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
5547     } else {
5548     out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
5549     out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
5550     }
5551     }
5552     return out;
5553     }
5554     //utf-16转utf-8
5555     function utf8to16(str) {
5556     var out, i, len, c;
5557     var char2, char3;
5558     out = "";
5559     len = str.length;
5560     i = 0;
5561     while(i < len) {
5562     c = str.charCodeAt(i++);
5563     switch(c >> 4)
5564     {
5565     case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
5566     // 0xxxxxxx
5567     out += str.charAt(i-1);
5568     break;
5569     case 12: case 13:
5570     // 110x xxxx 10xx xxxx
5571     char2 = str.charCodeAt(i++);
5572     out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
5573     break;
5574     case 14:
5575     // 1110 xxxx 10xx xxxx 10xx xxxx
5576     char2 = str.charCodeAt(i++);
5577     char3 = str.charCodeAt(i++);
5578     out += String.fromCharCode(((c & 0x0F) << 12) |
5579     ((char2 & 0x3F) << 6) |
5580     ((char3 & 0x3F) << 0));
5581     break;
5582     }
5583     }
5584     return out;
5585     } 

2、后台插件进行编码解析

Web界面

var iq=$iq({id:"iqwd_factorylogin",type:"get"}).
   c("query",{xmlns:"query-factorylogin"}).
   c("item").
   c("username").cht("张三");

Openfire插件

String username= Base64.getFromBase64(item.elementText("username"));

 1 package com.plugin.common;
 2
 3 import java.io.UnsupportedEncodingException;
 4
 5 import sun.misc.BASE64Decoder;
 6 import sun.misc.BASE64Encoder;
 7
 8 public class Base64 {
 9     // 加密
10     public static String getBase64(String str) {
11         byte[] b = null;
12         String s = null;
13         try {
14             b = str.getBytes("utf-8");
15         } catch (UnsupportedEncodingException e) {
16             e.printStackTrace();
17         }
18         if (b != null) {
19             s = new BASE64Encoder().encode(b);
20         }
21         return s;
22     }
23
24     // 解密
25     public static String getFromBase64(String s) {
26         byte[] b = null;
27         String result = null;
28         if (s != null) {
29             BASE64Decoder decoder = new BASE64Decoder();
30             try {
31                 b = decoder.decodeBuffer(s);
32                 result = new String(b, "utf-8");
33             } catch (Exception e) {
34                 e.printStackTrace();
35             }
36         }
37         return result;
38     }
39
40 }

时间: 2024-10-27 08:42:16

Openfire Strophe开发中文乱码问题的相关文章

wechat4j开发-中文乱码

微信接口返回内容的编码格式是UTF-8,中文乱码的出现是因为本地编码不是UTF-8导致的 具体解决方法如下: 1.使用sword-lang-2.0.0 UTF-8.jar替换sword-lang-1.2.0.jar包 2.在JDK的启动参数中添加如下参数 -Dfile.Encoding=UTF-8 备注:sword-lang-1.2.0.jar中未声明编码格式,默认会使用系统编码.sword-lang-2.0.0 UTF-8.jar中将所有编码都设置为 UTF-8

JAVA开发中文乱码的几个解决方案

一:html乱码或者引入的JS乱码 1:第一步,text file encoding 首先确保文件的保存格式要UTF-8,如在eclipse中,要在文件上点属性,确保这里选择UTF-8 注意,在eclispe中默认创建的js文件,其encoding是gbk的,注意一定改回来. 2:pageEncoding 确保html文件或者jsp文件的pageEncoding为utf-8. 注意,eclipse创建的默认的html或jsp文件,其pageEncoding是iso-8859-1,一定要改回来,如

在eclipse中进行php开发中文乱码问题

一    将eclipse中的编码都设为UTF-8,一共有3个地方需要设置 1,Window --> Preferences --> General --> Content Types,将这里面的每一项的默认编码都设为"UTF-8",特别是 Text 中的 PHP Source File ,在"Default encoding"的右边填写 2,Window --> Preferences --> General --> Edito

解决openfire在使用MySQL数据库后的中文乱码问题(转)

openfire是一个非常不错的IM服务器,而且是纯Java实现,具有多个平台的版本,他的数据存储可以采用多种数据库,如MySQL,Oracle等. 在实际使用时大家遇到最多的就是采用MySQL数据库后的中文乱码问题,这个问题十分有趣,而且从现象上可以看出openfire内部的一些机制. 实际问题是这样的:首先启动openfire服务器,然后利用客户端或直接登录到后台新建一个帐户,为该帐户指定一些中文的属性,如姓名等.如果不重启服务器,你永远不会觉得有什么不对的地方,因为所有的中文显示都是正常的

【开发必读】php与mysql中文乱码解决办法

原文地址:http://www.phpthinking.com/archives/320 MySQL对中文的支持程度还是很有限的,尤其是新手,但凡出现乱码问题,就会头大. 乱码问题1:用PHPmyAdmin操作MySQL数据库汉字显示正常,但用PHP网页显示MySQL数据时所有汉字都变成了?号. 症状:用PHPmyAdmin输入汉字正常,但当PHP网页显示MySQL数据时汉字就变成了?号,并且有多少个汉字就有多少个?号. 原因:没有在PHP网页中用代码告诉MySQL该以什么字符集输出汉字. 解决

IDEA2014开发Java程序以及中文乱码问题的解决

IDEA2014开发Java程序以及中文乱码问题的解决 我们知道IDEA是可以很强大的集成开发环境,不仅可以开发Web工程,还可以开发Java运行程序,但是在进行Java开发之前,我们需要对IDEA做一些配置工作.步骤如下: 下载IDEA2014下载地址,过期私聊我(http://pan.baidu.com/s/1jIfbtdW) 下载JDK64位机(http://download.oracle.com/otn-pub/java/jdk/8u77-b03/jdk-8u77-windows-x64

微信公众号开发--获取用户信息中文乱码的解决方案

在微信开发中我们会经常需要获取用户的信息. 微信给我们提供了获取用户信息的api, 地址为 https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN 将其中的access_token替换为我们的access_token openid为关注用户的openid就可以获取到用户的信息了. 问题描述 不过获取到的数据却是中文乱码 而用Java程序获取的结果也是一样的

Qt开发中文显示乱码

Qt开发中文显示乱码 来源 https://www.jianshu.com/p/ed269df8104d 参考 https://blog.csdn.net/J_H_C/article/details/93882284 为什么会出现乱码 首先,我们需要有的概念是乱码的问题是由编码和解码方式引起的.涉及到编码方式的地方有3个: 源码字符集 执行字符集 运行环境字符集 源码字符集确切的说是编译器认为源码文件的编码方式,执行字符集是可执行程序采用的编码方式,而运行环境字符集则是环境支持的编码方式.编译程

java开发中中文乱码总结

1.jsp页面内容显示乱码 这种乱码原因很简单,一般的工具或解码程序对中文字符解析时采用默认的解码方式: <%@ page contentType="text/html; charset=ISO-8859-1"%> 我们只需修改其编码方式即可,如下: <%@ page contentType="text/html; charset=UTF-8"%> 字符集:UTF-8 > GBK > GB2312 2.jsp 与 Servlet