]> git.mxchange.org Git - friendica-addons.git/blob - jappixmini/jappix/js/jsjac.js
Merge pull request #520 from rabuzarus/20180206_-_membersince_frio_support
[friendica-addons.git] / jappixmini / jappix / js / jsjac.js
1 /*
2
3 Jappix - An open social platform
4 This is the JSJaC library for Jappix (from trunk)
5
6 -------------------------------------------------
7
8 Licenses: Mozilla Public License version 1.1, GNU GPL, AGPL
9 Authors: Stefan Strigler, Vanaryon, Zash
10 Last revision: 25/08/11
11
12 */
13
14 /**
15  * @fileoverview Magic dependency loading. Taken from script.aculo.us
16  * and modified to break it.
17  * @author Stefan Strigler steve@zeank.in-berlin.de 
18  * @version $Revision$
19  */
20
21 var JSJaC = {
22   Version: '$Rev$',
23   bind: function(fn, obj, optArg) {
24     return function(arg) {
25       return fn.apply(obj, [arg, optArg]);
26     };
27   }
28 };
29
30 if (typeof JSJaCConnection == 'undefined')
31   JSJaC.load();
32
33
34
35 /* Copyright 2006 Erik Arvidsson
36  *
37  * Licensed under the Apache License, Version 2.0 (the "License"); you
38  * may not use this file except in compliance with the License.  You
39  * may obtain a copy of the License at
40  *
41  * http://www.apache.org/licenses/LICENSE-2.0
42  *
43  * Unless required by applicable law or agreed to in writing, software
44  * distributed under the License is distributed on an "AS IS" BASIS,
45  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
46  * implied.  See the License for the specific language governing
47  * permissions and limitations under the License.
48  */
49
50 /**
51  * @fileoverview Wrapper to make working with XmlHttpRequest and the
52  * DOM more convenient (cross browser compliance).
53  * this code is taken from
54  * http://webfx.eae.net/dhtml/xmlextras/xmlextras.html
55  * @author Stefan Strigler steve@zeank.in-berlin.de
56  * @version $Revision$
57  */
58
59 /**
60  * XmlHttp factory
61  * @private
62  */
63 function XmlHttp() {}
64
65 /**
66  * creates a cross browser compliant XmlHttpRequest object
67  */
68 XmlHttp.create = function () {
69   try {
70     // Are we cross-domain?
71     if(((BOSH_PROXY == 'on') || (HOST_BOSH_MINI)) && (typeof jXHR == "function")) {
72         // Able to use CORS?
73         if (window.XMLHttpRequest) {
74           var req = new XMLHttpRequest();
75           
76           if (req.withCredentials !== undefined)
77             return req;
78         }
79         
80         return new jXHR();
81     }
82     // Might be local-domain?
83     if (window.XMLHttpRequest) {
84       var req = new XMLHttpRequest();
85       
86       // some versions of Moz do not support the readyState property
87       // and the onreadystate event so we patch it!
88       if (req.readyState == null) {
89         req.readyState = 1;
90         req.addEventListener("load", function () {
91                                req.readyState = 4;
92                                if (typeof req.onreadystatechange == "function")
93                                  req.onreadystatechange();
94                              }, false);
95       }
96       
97       return req;
98     }
99     if (window.ActiveXObject) {
100       return new ActiveXObject(XmlHttp.getPrefix() + ".XmlHttp");
101     }
102   }
103   catch (ex) {}
104   // fell through
105   throw new Error("Your browser does not support XmlHttp objects");
106 };
107
108 /**
109  * used to find the Automation server name
110  * @private
111  */
112 XmlHttp.getPrefix = function() {
113   if (XmlHttp.prefix) // I know what you did last summer
114     return XmlHttp.prefix;
115  
116   var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
117   var o;
118   for (var i = 0; i < prefixes.length; i++) {
119     try {
120       // try to create the objects
121       o = new ActiveXObject(prefixes[i] + ".XmlHttp");
122       return XmlHttp.prefix = prefixes[i];
123     }
124     catch (ex) {};
125   }
126  
127   throw new Error("Could not find an installed XML parser");
128 };
129
130
131 /**
132  * XmlDocument factory
133  * @private
134  */
135 function XmlDocument() {}
136
137 XmlDocument.create = function (name,ns) {
138   name = name || 'foo';
139   ns = ns || '';
140
141   try {
142     var doc;
143     // DOM2
144     if (document.implementation && document.implementation.createDocument) {
145       doc = document.implementation.createDocument(ns, name, null);
146       // some versions of Moz do not support the readyState property
147       // and the onreadystate event so we patch it!
148       if (doc.readyState == null) {
149         doc.readyState = 1;
150         doc.addEventListener("load", function () {
151                                doc.readyState = 4;
152                                if (typeof doc.onreadystatechange == "function")
153                                  doc.onreadystatechange();
154                              }, false);
155       }
156     } else if (window.ActiveXObject) {
157       doc = new ActiveXObject(XmlDocument.getPrefix() + ".DomDocument");
158     }
159    
160     if (!doc.documentElement || doc.documentElement.tagName != name ||
161         (doc.documentElement.namespaceURI &&
162          doc.documentElement.namespaceURI != ns)) {
163           try {
164             if (ns != '')
165               doc.appendChild(doc.createElement(name)).
166                 setAttribute('xmlns',ns);
167             else
168               doc.appendChild(doc.createElement(name));
169           } catch (dex) {
170             doc = document.implementation.createDocument(ns,name,null);
171            
172             if (doc.documentElement == null)
173               doc.appendChild(doc.createElement(name));
174
175              // fix buggy opera 8.5x
176             if (ns != '' &&
177                 doc.documentElement.getAttribute('xmlns') != ns) {
178               doc.documentElement.setAttribute('xmlns',ns);
179             }
180           }
181         }
182    
183     return doc;
184   }
185   catch (ex) { }
186   throw new Error("Your browser does not support XmlDocument objects");
187 };
188
189 /**
190  * used to find the Automation server name
191  * @private
192  */
193 XmlDocument.getPrefix = function() {
194   if (XmlDocument.prefix)
195     return XmlDocument.prefix;
196
197   var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
198   var o;
199   for (var i = 0; i < prefixes.length; i++) {
200     try {
201       // try to create the objects
202       o = new ActiveXObject(prefixes[i] + ".DomDocument");
203       return XmlDocument.prefix = prefixes[i];
204     }
205     catch (ex) {};
206   }
207  
208   throw new Error("Could not find an installed XML parser");
209 };
210
211
212 // Create the loadXML method
213 if (typeof(Document) != 'undefined' && window.DOMParser) {
214
215   /**
216    * XMLDocument did not extend the Document interface in some
217    * versions of Mozilla.
218    * @private
219    */
220   Document.prototype.loadXML = function (s) {
221         
222     // parse the string to a new doc
223     var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
224         
225     // remove all initial children
226     while (this.hasChildNodes())
227       this.removeChild(this.lastChild);
228                 
229     // insert and import nodes
230     for (var i = 0; i < doc2.childNodes.length; i++) {
231       this.appendChild(this.importNode(doc2.childNodes[i], true));
232     }
233   };
234  }
235
236 // Create xml getter for Mozilla
237 if (window.XMLSerializer &&
238     window.Node && Node.prototype && Node.prototype.__defineGetter__) {
239
240   /**
241    * xml getter
242    *
243    * This serializes the DOM tree to an XML String
244    *
245    * Usage: var sXml = oNode.xml
246    * @deprecated
247    * @private
248    */
249   // XMLDocument did not extend the Document interface in some versions
250   // of Mozilla. Extend both!
251   XMLDocument.prototype.__defineGetter__("xml", function () {
252                                            return (new XMLSerializer()).serializeToString(this);
253                                          });
254   /**
255    * xml getter
256    *
257    * This serializes the DOM tree to an XML String
258    *
259    * Usage: var sXml = oNode.xml
260    * @deprecated
261    * @private
262    */
263   Document.prototype.__defineGetter__("xml", function () {
264                                         return (new XMLSerializer()).serializeToString(this);
265                                       });
266
267   /**
268    * xml getter
269    *
270    * This serializes the DOM tree to an XML String
271    *
272    * Usage: var sXml = oNode.xml
273    * @deprecated
274    * @private
275    */
276   Node.prototype.__defineGetter__("xml", function () {
277                                     return (new XMLSerializer()).serializeToString(this);
278                                   });
279  }
280
281
282 /**
283  * @fileoverview Collection of functions to make live easier
284  * @author Stefan Strigler
285  * @version $Revision$
286  */
287
288 /**
289  * Convert special chars to HTML entities
290  * @addon
291  * @return The string with chars encoded for HTML
292  * @type String
293  */
294 String.prototype.htmlEnc = function() {
295   if(!this)
296     return this;
297   
298   var str = this.replace(/&/g,"&amp;");
299   str = str.replace(/</g,"&lt;");
300   str = str.replace(/>/g,"&gt;");
301   str = str.replace(/\"/g,"&quot;");
302   str = str.replace(/\n/g,"<br />");
303   return str;
304 };
305
306 /**
307  * Convert HTML entities to special chars
308  * @addon
309  * @return The normal string
310  * @type String
311  */
312 String.prototype.revertHtmlEnc = function() {
313   if(!this)
314     return this;
315   
316   var str = this.replace(/&amp;/gi,'&');
317   str = str.replace(/&lt;/gi,'<');
318   str = str.replace(/&gt;/gi,'>');
319   str = str.replace(/&quot;/gi,'\"');
320   str = str.replace(/<br( )?(\/)?>/gi,'\n');
321   return str;
322 };
323
324 /**
325  * Converts from jabber timestamps to JavaScript Date objects
326  * @addon
327  * @param {String} ts A string representing a jabber datetime timestamp as
328  * defined by {@link http://www.xmpp.org/extensions/xep-0082.html XEP-0082}
329  * @return A javascript Date object corresponding to the jabber DateTime given
330  * @type Date
331  */
332 Date.jab2date = function(ts) {
333   // Get the UTC date
334   var date = new Date(Date.UTC(ts.substr(0,4),ts.substr(5,2)-1,ts.substr(8,2),ts.substr(11,2),ts.substr(14,2),ts.substr(17,2)));
335   
336   if (ts.substr(ts.length-6,1) != 'Z') { // there's an offset
337     var date_offset = date.getTimezoneOffset() * 60 * 1000;
338     var offset = new Date();
339     offset.setTime(0);
340     offset.setUTCHours(ts.substr(ts.length-5,2));
341     offset.setUTCMinutes(ts.substr(ts.length-2,2));
342     if (ts.substr(ts.length-6,1) == '+')
343       date.setTime(date.getTime() + offset.getTime() + date_offset);
344     else if (ts.substr(ts.length-6,1) == '-')
345       date.setTime(date.getTime() - offset.getTime() + date_offset);
346   }
347   return date;
348 };
349
350 /**
351  * Takes a timestamp in the form of 2004-08-13T12:07:04+02:00 as argument
352  * and converts it to some sort of humane readable format
353  * @addon
354  */
355 Date.hrTime = function(ts) {
356   return Date.jab2date(ts).toLocaleString();
357 };
358
359 /**
360  * somewhat opposit to {@link #hrTime}
361  * expects a javascript Date object as parameter and returns a jabber
362  * date string conforming to
363  * {@link http://www.xmpp.org/extensions/xep-0082.html XEP-0082}
364  * @see #hrTime
365  * @return The corresponding jabber DateTime string
366  * @type String
367  */
368 Date.prototype.jabberDate = function() {
369   var padZero = function(i) {
370     if (i < 10) return "0" + i;
371     return i;
372   };
373
374   var jDate = this.getUTCFullYear() + "-";
375   jDate += padZero(this.getUTCMonth()+1) + "-";
376   jDate += padZero(this.getUTCDate()) + "T";
377   jDate += padZero(this.getUTCHours()) + ":";
378   jDate += padZero(this.getUTCMinutes()) + ":";
379   jDate += padZero(this.getUTCSeconds()) + "Z";
380
381   return jDate;
382 };
383
384 /**
385  * Determines the maximum of two given numbers
386  * @addon
387  * @param {Number} A a number
388  * @param {Number} B another number
389  * @return the maximum of A and B
390  * @type Number
391  */
392 Number.max = function(A, B) {
393   return (A > B)? A : B;
394 };
395
396 Number.min = function(A, B) {
397   return (A < B)? A : B;
398 };
399
400
401 /* Copyright (c) 1998 - 2007, Paul Johnston & Contributors
402  * All rights reserved.
403  *
404  * Redistribution and use in source and binary forms, with or without
405  * modification, are permitted provided that the following conditions
406  * are met:
407  *
408  * Redistributions of source code must retain the above copyright
409  * notice, this list of conditions and the following
410  * disclaimer. Redistributions in binary form must reproduce the above
411  * copyright notice, this list of conditions and the following
412  * disclaimer in the documentation and/or other materials provided
413  * with the distribution.
414  *
415  * Neither the name of the author nor the names of its contributors
416  * may be used to endorse or promote products derived from this
417  * software without specific prior written permission.
418  *
419  *
420  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
421  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
422  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
423  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
424  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
425  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
426  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
427  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
428  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
429  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
430  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
431  * OF THE POSSIBILITY OF SUCH DAMAGE.
432  *
433  */
434
435 /**
436  * @fileoverview Collection of MD5 and SHA1 hashing and encoding
437  * methods.
438  * @author Stefan Strigler steve@zeank.in-berlin.de
439  * @version $Revision$
440  */
441
442 /*
443  * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
444  * in FIPS PUB 180-1
445  * Version 2.1a Copyright Paul Johnston 2000 - 2002.
446  * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
447  * Distributed under the BSD License
448  * See http://pajhome.org.uk/crypt/md5 for details.
449  */
450
451 /*
452  * Configurable variables. You may need to tweak these to be compatible with
453  * the server-side, but the defaults work in most cases.
454  */
455 var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
456 var b64pad  = "="; /* base-64 pad character. "=" for strict RFC compliance   */
457 var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
458
459 /*
460  * These are the functions you'll usually want to call
461  * They take string arguments and return either hex or base-64 encoded strings
462  */
463 function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
464 function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
465 function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
466 function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
467 function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
468 function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
469
470 /*
471  * Perform a simple self-test to see if the VM is working
472  */
473 function sha1_vm_test()
474 {
475   return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
476 }
477
478 /*
479  * Calculate the SHA-1 of an array of big-endian words, and a bit length
480  */
481 function core_sha1(x, len)
482 {
483   /* append padding */
484   x[len >> 5] |= 0x80 << (24 - len % 32);
485   x[((len + 64 >> 9) << 4) + 15] = len;
486
487   var w = Array(80);
488   var a =  1732584193;
489   var b = -271733879;
490   var c = -1732584194;
491   var d =  271733878;
492   var e = -1009589776;
493
494   for(var i = 0; i < x.length; i += 16)
495     {
496       var olda = a;
497       var oldb = b;
498       var oldc = c;
499       var oldd = d;
500       var olde = e;
501
502       for(var j = 0; j < 80; j++)
503         {
504           if(j < 16) w[j] = x[i + j];
505           else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
506           var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
507                            safe_add(safe_add(e, w[j]), sha1_kt(j)));
508           e = d;
509           d = c;
510           c = rol(b, 30);
511           b = a;
512           a = t;
513         }
514
515       a = safe_add(a, olda);
516       b = safe_add(b, oldb);
517       c = safe_add(c, oldc);
518       d = safe_add(d, oldd);
519       e = safe_add(e, olde);
520     }
521   return Array(a, b, c, d, e);
522
523 }
524
525 /*
526  * Perform the appropriate triplet combination function for the current
527  * iteration
528  */
529 function sha1_ft(t, b, c, d)
530 {
531   if(t < 20) return (b & c) | ((~b) & d);
532   if(t < 40) return b ^ c ^ d;
533   if(t < 60) return (b & c) | (b & d) | (c & d);
534   return b ^ c ^ d;
535 }
536
537 /*
538  * Determine the appropriate additive constant for the current iteration
539  */
540 function sha1_kt(t)
541 {
542   return (t < 20) ?  1518500249 : (t < 40) ?  1859775393 :
543     (t < 60) ? -1894007588 : -899497514;
544 }
545
546 /*
547  * Calculate the HMAC-SHA1 of a key and some data
548  */
549 function core_hmac_sha1(key, data)
550 {
551   var bkey = str2binb(key);
552   if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
553
554   var ipad = Array(16), opad = Array(16);
555   for(var i = 0; i < 16; i++)
556     {
557       ipad[i] = bkey[i] ^ 0x36363636;
558       opad[i] = bkey[i] ^ 0x5C5C5C5C;
559     }
560
561   var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
562   return core_sha1(opad.concat(hash), 512 + 160);
563 }
564
565 /*
566  * Bitwise rotate a 32-bit number to the left.
567  */
568 function rol(num, cnt)
569 {
570   return (num << cnt) | (num >>> (32 - cnt));
571 }
572
573 /*
574  * Convert an 8-bit or 16-bit string to an array of big-endian words
575  * In 8-bit function, characters >255 have their hi-byte silently ignored.
576  */
577 function str2binb(str)
578 {
579   var bin = Array();
580   var mask = (1 << chrsz) - 1;
581   for(var i = 0; i < str.length * chrsz; i += chrsz)
582     bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
583   return bin;
584 }
585
586 /*
587  * Convert an array of big-endian words to a string
588  */
589 function binb2str(bin)
590 {
591   var str = "";
592   var mask = (1 << chrsz) - 1;
593   for(var i = 0; i < bin.length * 32; i += chrsz)
594     str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
595   return str;
596 }
597
598 /*
599  * Convert an array of big-endian words to a hex string.
600  */
601 function binb2hex(binarray)
602 {
603   var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
604   var str = "";
605   for(var i = 0; i < binarray.length * 4; i++)
606     {
607       str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
608         hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8  )) & 0xF);
609     }
610   return str;
611 }
612
613 /*
614  * Convert an array of big-endian words to a base-64 string
615  */
616 function binb2b64(binarray)
617 {
618   var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
619   var str = "";
620   for(var i = 0; i < binarray.length * 4; i += 3)
621     {
622       var triplet = (((binarray[i   >> 2] >> 8 * (3 -  i   %4)) & 0xFF) << 16)
623         | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
624         |  ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
625       for(var j = 0; j < 4; j++)
626         {
627           if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
628           else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
629         }
630     }
631   return str.replace(/AAA\=(\=*?)$/,'$1'); // cleans garbage chars at end of string
632 }
633
634 /*
635  * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
636  * Digest Algorithm, as defined in RFC 1321.
637  * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
638  * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
639  * Distributed under the BSD License
640  * See http://pajhome.org.uk/crypt/md5 for more info.
641  */
642
643 /*
644  * Configurable variables. You may need to tweak these to be compatible with
645  * the server-side, but the defaults work in most cases.
646  */
647 // var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
648 // var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
649 // var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
650
651 /*
652  * These are the functions you'll usually want to call
653  * They take string arguments and return either hex or base-64 encoded strings
654  */
655 function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
656 function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
657 function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
658 function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
659 function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
660 function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
661
662 /*
663  * Perform a simple self-test to see if the VM is working
664  */
665 function md5_vm_test()
666 {
667   return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
668 }
669
670 /*
671  * Calculate the MD5 of an array of little-endian words, and a bit length
672  */
673 function core_md5(x, len)
674 {
675   /* append padding */
676   x[len >> 5] |= 0x80 << ((len) % 32);
677   x[(((len + 64) >>> 9) << 4) + 14] = len;
678
679   var a =  1732584193;
680   var b = -271733879;
681   var c = -1732584194;
682   var d =  271733878;
683
684   for(var i = 0; i < x.length; i += 16)
685   {
686     var olda = a;
687     var oldb = b;
688     var oldc = c;
689     var oldd = d;
690
691     a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
692     d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
693     c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
694     b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
695     a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
696     d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
697     c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
698     b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
699     a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
700     d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
701     c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
702     b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
703     a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
704     d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
705     c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
706     b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
707
708     a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
709     d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
710     c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
711     b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
712     a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
713     d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
714     c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
715     b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
716     a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
717     d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
718     c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
719     b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
720     a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
721     d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
722     c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
723     b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
724
725     a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
726     d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
727     c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
728     b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
729     a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
730     d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
731     c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
732     b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
733     a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
734     d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
735     c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
736     b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
737     a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
738     d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
739     c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
740     b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
741
742     a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
743     d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
744     c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
745     b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
746     a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
747     d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
748     c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
749     b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
750     a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
751     d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
752     c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
753     b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
754     a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
755     d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
756     c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
757     b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
758
759     a = safe_add(a, olda);
760     b = safe_add(b, oldb);
761     c = safe_add(c, oldc);
762     d = safe_add(d, oldd);
763   }
764   return Array(a, b, c, d);
765
766 }
767
768 /*
769  * These functions implement the four basic operations the algorithm uses.
770  */
771 function md5_cmn(q, a, b, x, s, t)
772 {
773   return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
774 }
775 function md5_ff(a, b, c, d, x, s, t)
776 {
777   return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
778 }
779 function md5_gg(a, b, c, d, x, s, t)
780 {
781   return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
782 }
783 function md5_hh(a, b, c, d, x, s, t)
784 {
785   return md5_cmn(b ^ c ^ d, a, b, x, s, t);
786 }
787 function md5_ii(a, b, c, d, x, s, t)
788 {
789   return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
790 }
791
792 /*
793  * Calculate the HMAC-MD5, of a key and some data
794  */
795 function core_hmac_md5(key, data)
796 {
797   var bkey = str2binl(key);
798   if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
799
800   var ipad = Array(16), opad = Array(16);
801   for(var i = 0; i < 16; i++)
802   {
803     ipad[i] = bkey[i] ^ 0x36363636;
804     opad[i] = bkey[i] ^ 0x5C5C5C5C;
805   }
806
807   var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
808   return core_md5(opad.concat(hash), 512 + 128);
809 }
810
811 /*
812  * Add integers, wrapping at 2^32. This uses 16-bit operations internally
813  * to work around bugs in some JS interpreters.
814  */
815 function safe_add(x, y)
816 {
817   var lsw = (x & 0xFFFF) + (y & 0xFFFF);
818   var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
819   return (msw << 16) | (lsw & 0xFFFF);
820 }
821
822 /*
823  * Bitwise rotate a 32-bit number to the left.
824  */
825 function bit_rol(num, cnt)
826 {
827   return (num << cnt) | (num >>> (32 - cnt));
828 }
829
830 /*
831  * Convert a string to an array of little-endian words
832  * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
833  */
834 function str2binl(str)
835 {
836   var bin = Array();
837   var mask = (1 << chrsz) - 1;
838   for(var i = 0; i < str.length * chrsz; i += chrsz)
839     bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
840   return bin;
841 }
842
843 /*
844  * Convert an array of little-endian words to a string
845  */
846 function binl2str(bin)
847 {
848   var str = "";
849   var mask = (1 << chrsz) - 1;
850   for(var i = 0; i < bin.length * 32; i += chrsz)
851     str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
852   return str;
853 }
854
855 /*
856  * Convert an array of little-endian words to a hex string.
857  */
858 function binl2hex(binarray)
859 {
860   var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
861   var str = "";
862   for(var i = 0; i < binarray.length * 4; i++)
863   {
864     str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
865            hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
866   }
867   return str;
868 }
869
870 /*
871  * Convert an array of little-endian words to a base-64 string
872  */
873 function binl2b64(binarray)
874 {
875   var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
876   var str = "";
877   for(var i = 0; i < binarray.length * 4; i += 3)
878   {
879     var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
880                 | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
881                 |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
882     for(var j = 0; j < 4; j++)
883     {
884       if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
885       else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
886     }
887   }
888   return str;
889 }
890
891 /* #############################################################################
892    UTF-8 Decoder and Encoder
893    base64 Encoder and Decoder
894    written by Tobias Kieslich, justdreams
895    Contact: tobias@justdreams.de                                http://www.justdreams.de/
896    ############################################################################# */
897
898 // returns an array of byterepresenting dezimal numbers which represent the
899 // plaintext in an UTF-8 encoded version. Expects a string.
900 // This function includes an exception management for those nasty browsers like
901 // NN401, which returns negative decimal numbers for chars>128. I hate it!!
902 // This handling is unfortunately limited to the user's charset. Anyway, it works
903 // in most of the cases! Special signs with an unicode>256 return numbers, which
904 // can not be converted to the actual unicode and so not to the valid utf-8
905 // representation. Anyway, this function does always return values which can not
906 // misinterpretd by RC4 or base64 en- or decoding, because every value is >0 and
907 // <255!!
908 // Arrays are faster and easier to handle in b64 encoding or encrypting....
909 function utf8t2d(t)
910 {
911   t = t.replace(/\r\n/g,"\n");
912   var d=new Array; var test=String.fromCharCode(237);
913   if (test.charCodeAt(0) < 0)
914     for(var n=0; n<t.length; n++)
915       {
916         var c=t.charCodeAt(n);
917         if (c>0)
918           d[d.length]= c;
919         else {
920           d[d.length]= (((256+c)>>6)|192);
921           d[d.length]= (((256+c)&63)|128);}
922       }
923   else
924     for(var n=0; n<t.length; n++)
925       {
926         var c=t.charCodeAt(n);
927         // all the signs of asci => 1byte
928         if (c<128)
929           d[d.length]= c;
930         // all the signs between 127 and 2047 => 2byte
931         else if((c>127) && (c<2048)) {
932           d[d.length]= ((c>>6)|192);
933           d[d.length]= ((c&63)|128);}
934         // all the signs between 2048 and 66536 => 3byte
935         else {
936           d[d.length]= ((c>>12)|224);
937           d[d.length]= (((c>>6)&63)|128);
938           d[d.length]= ((c&63)|128);}
939       }
940   return d;
941 }
942         
943 // returns plaintext from an array of bytesrepresenting dezimal numbers, which
944 // represent an UTF-8 encoded text; browser which does not understand unicode
945 // like NN401 will show "?"-signs instead
946 // expects an array of byterepresenting decimals; returns a string
947 function utf8d2t(d)
948 {
949   var r=new Array; var i=0;
950   while(i<d.length)
951     {
952       if (d[i]<128) {
953         r[r.length]= String.fromCharCode(d[i]); i++;}
954       else if((d[i]>191) && (d[i]<224)) {
955         r[r.length]= String.fromCharCode(((d[i]&31)<<6) | (d[i+1]&63)); i+=2;}
956       else {
957         r[r.length]= String.fromCharCode(((d[i]&15)<<12) | ((d[i+1]&63)<<6) | (d[i+2]&63)); i+=3;}
958     }
959   return r.join("");
960 }
961
962 // included in <body onload="b64arrays"> it creates two arrays which makes base64
963 // en- and decoding faster
964 // this speed is noticeable especially when coding larger texts (>5k or so)
965 function b64arrays() {
966   var b64s='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
967   b64 = new Array();f64 =new Array();
968   for (var i=0; i<b64s.length ;i++) {
969     b64[i] = b64s.charAt(i);
970     f64[b64s.charAt(i)] = i;
971   }
972 }
973
974 // creates a base64 encoded text out of an array of byerepresenting dezimals
975 // it is really base64 :) this makes serversided handling easier
976 // expects an array; returns a string
977 function b64d2t(d) {
978   var r=new Array; var i=0; var dl=d.length;
979   // this is for the padding
980   if ((dl%3) == 1) {
981     d[d.length] = 0; d[d.length] = 0;}
982   if ((dl%3) == 2)
983     d[d.length] = 0;
984   // from here conversion
985   while (i<d.length)
986     {
987       r[r.length] = b64[d[i]>>2];
988       r[r.length] = b64[((d[i]&3)<<4) | (d[i+1]>>4)];
989       r[r.length] = b64[((d[i+1]&15)<<2) | (d[i+2]>>6)];
990       r[r.length] = b64[d[i+2]&63];
991       i+=3;
992     }
993   // this is again for the padding
994   if ((dl%3) == 1)
995     r[r.length-1] = r[r.length-2] = "=";
996   if ((dl%3) == 2)
997     r[r.length-1] = "=";
998   // we join the array to return a textstring
999   var t=r.join("");
1000   return t;
1001 }
1002
1003 // returns array of byterepresenting numbers created of an base64 encoded text
1004 // it is still the slowest function in this modul; I hope I can make it faster
1005 // expects string; returns an array
1006 function b64t2d(t) {
1007   var d=new Array; var i=0;
1008   // here we fix this CRLF sequenz created by MS-OS; arrrgh!!!
1009   t=t.replace(/\n|\r/g,""); t=t.replace(/=/g,"");
1010   while (i<t.length)
1011     {
1012       d[d.length] = (f64[t.charAt(i)]<<2) | (f64[t.charAt(i+1)]>>4);
1013       d[d.length] = (((f64[t.charAt(i+1)]&15)<<4) | (f64[t.charAt(i+2)]>>2));
1014       d[d.length] = (((f64[t.charAt(i+2)]&3)<<6) | (f64[t.charAt(i+3)]));
1015       i+=4;
1016     }
1017   if (t.length%4 == 2)
1018     d = d.slice(0, d.length-2);
1019   if (t.length%4 == 3)
1020     d = d.slice(0, d.length-1);
1021   return d;
1022 }
1023
1024 b64arrays();
1025
1026 b64decode = function(s) {
1027   return utf8d2t(b64t2d(s));
1028 }
1029
1030 b64encode = function(s) {
1031   return b64d2t(utf8t2d(s));
1032 }
1033
1034 function cnonce(size) {
1035   var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1036   var cnonce = '';
1037   for (var i=0; i<size; i++) {
1038     cnonce += tab.charAt(Math.round(Math.random(new Date().getTime())*(tab.length-1)));
1039   }
1040   return cnonce;
1041 }
1042
1043
1044
1045 JSJAC_HAVEKEYS = true;          // whether to use keys
1046 JSJAC_NKEYS    = 16;            // number of keys to generate
1047 JSJAC_INACTIVITY = 300;         // qnd hack to make suspend/resume 
1048                                     // work more smoothly with polling
1049 JSJAC_ERR_COUNT = 10;           // number of retries in case of connection
1050                                     // errors
1051
1052 JSJAC_ALLOW_PLAIN = true;       // whether to allow plaintext logins
1053
1054 JSJAC_CHECKQUEUEINTERVAL = 100;   // msecs to poll send queue
1055 JSJAC_CHECKINQUEUEINTERVAL = 100; // msecs to poll incoming queue
1056 JSJAC_TIMERVAL = 2000;          // default polling interval
1057
1058 // Options specific to HTTP Binding (BOSH)
1059 JSJACHBC_MAX_HOLD = 1;          // default for number of connections held by 
1060                                     // connection manager 
1061 JSJACHBC_MAX_WAIT = 20;        // default 'wait' param - how long an idle connection
1062                                     // should be held by connection manager
1063
1064 JSJACHBC_BOSH_VERSION  = "1.6";
1065 JSJACHBC_USE_BOSH_VER  = true;
1066
1067 JSJACHBC_MAXPAUSE = 20;        // how long a suspend/resume cycle may take
1068
1069 /*** END CONFIG ***/
1070
1071
1072 /* Copyright (c) 2005-2007 Sam Stephenson
1073  *
1074  * Permission is hereby granted, free of charge, to any person
1075  * obtaining a copy of this software and associated documentation
1076  * files (the "Software"), to deal in the Software without
1077  * restriction, including without limitation the rights to use, copy,
1078  * modify, merge, publish, distribute, sublicense, and/or sell copies
1079  * of the Software, and to permit persons to whom the Software is
1080  * furnished to do so, subject to the following conditions:
1081  *
1082  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1083  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1084  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1085  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
1086  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
1087  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1088  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1089  * SOFTWARE.
1090  */
1091
1092 /*
1093   json.js
1094   taken from prototype.js, made static
1095 */
1096 function JSJaCJSON() {}
1097 JSJaCJSON.toString = function (obj) {
1098   var m = {
1099     '\b': '\\b',
1100     '\t': '\\t',
1101     '\n': '\\n',
1102     '\f': '\\f',
1103     '\r': '\\r',
1104     '"' : '\\"',
1105     '\\': '\\\\'
1106   },
1107   s = {
1108     array: function (x) {
1109       var a = ['['], b, f, i, l = x.length, v;
1110       for (i = 0; i < l; i += 1) {
1111         v = x[i];
1112         f = s[typeof v];
1113         if (f) {
1114           try {
1115             v = f(v);
1116             if (typeof v == 'string') {
1117               if (b) {
1118                 a[a.length] = ',';
1119               }
1120               a[a.length] = v;
1121               b = true;
1122             }
1123           } catch(e) { 
1124           }
1125         }
1126       }
1127       a[a.length] = ']';
1128       return a.join('');
1129     },
1130     'boolean': function (x) {
1131       return String(x);
1132     },
1133     'null': function (x) {
1134       return "null";
1135     },
1136     number: function (x) {
1137       return isFinite(x) ? String(x) : 'null';
1138     },
1139     object: function (x) {
1140       if (x) {
1141         if (x instanceof Array) {
1142           return s.array(x);
1143         }
1144         var a = ['{'], b, f, i, v;
1145         for (i in x) {
1146           if (x.hasOwnProperty(i)) {
1147             v = x[i];
1148             f = s[typeof v];
1149             if (f) {
1150               try {
1151                 v = f(v);
1152                 if (typeof v == 'string') {
1153                   if (b) {
1154                     a[a.length] = ',';
1155                   }
1156                   a.push(s.string(i), ':', v);
1157                   b = true;
1158                 }
1159               } catch(e) {
1160               }
1161             }
1162           }
1163         }
1164          
1165         a[a.length] = '}';
1166         return a.join('');
1167       }
1168       return 'null';
1169     },
1170     string: function (x) {
1171       if (/["\\\x00-\x1f]/.test(x)) {
1172                     x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
1173           var c = m[b];
1174           if (c) {
1175             return c;
1176           }
1177           c = b.charCodeAt();
1178           return '\\u00' +
1179           Math.floor(c / 16).toString(16) +
1180           (c % 16).toString(16);
1181         });
1182   }
1183   return '"' + x + '"';
1184 }
1185   };
1186
1187 switch (typeof(obj)) {
1188  case 'object':
1189    return s.object(obj);
1190  case 'array':
1191    return s.array(obj);
1192    
1193  }
1194 };
1195
1196 JSJaCJSON.parse = function (str) {
1197   try {
1198     return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
1199                                                        str.replace(/"(\\.|[^"\\])*"/g, ''))) &&
1200             eval('(' + str + ')');
1201     } catch (e) {
1202         return false;
1203     }
1204 };
1205
1206
1207 /**
1208  * @fileoverview This file contains all things that make life easier when
1209  * dealing with JIDs
1210  * @author Stefan Strigler
1211  * @version $Revision$
1212  */
1213
1214 /**
1215  * list of forbidden chars for nodenames
1216  * @private
1217  */
1218 var JSJACJID_FORBIDDEN = ['"',' ','&','\'','/',':','<','>','@'];
1219
1220 /**
1221  * Creates a new JSJaCJID object
1222  * @class JSJaCJID models xmpp jid objects
1223  * @constructor
1224  * @param {Object} jid jid may be either of type String or a JID represented
1225  * by JSON with fields 'node', 'domain' and 'resource'
1226  * @throws JSJaCJIDInvalidException Thrown if jid is not valid
1227  * @return a new JSJaCJID object
1228  */
1229 function JSJaCJID(jid) {
1230   /**
1231    *@private
1232    */
1233   this._node = '';
1234   /**
1235    *@private
1236    */
1237   this._domain = '';
1238   /**
1239    *@private
1240    */
1241   this._resource = '';
1242
1243   if (typeof(jid) == 'string') {
1244     if (jid.indexOf('@') != -1) {
1245         this.setNode(jid.substring(0,jid.indexOf('@')));
1246         jid = jid.substring(jid.indexOf('@')+1);
1247     }
1248     if (jid.indexOf('/') != -1) {
1249       this.setResource(jid.substring(jid.indexOf('/')+1));
1250       jid = jid.substring(0,jid.indexOf('/'));
1251     }
1252     this.setDomain(jid);
1253   } else {
1254     this.setNode(jid.node);
1255     this.setDomain(jid.domain);
1256     this.setResource(jid.resource);
1257   }
1258 }
1259
1260
1261 /**
1262  * Gets the node part of the jid
1263  * @return A string representing the node name
1264  * @type String
1265  */
1266 JSJaCJID.prototype.getNode = function() { return this._node; };
1267
1268 /**
1269  * Gets the domain part of the jid
1270  * @return A string representing the domain name
1271  * @type String
1272  */
1273 JSJaCJID.prototype.getDomain = function() { return this._domain; };
1274
1275 /**
1276  * Gets the resource part of the jid
1277  * @return A string representing the resource
1278  * @type String
1279  */
1280 JSJaCJID.prototype.getResource = function() { return this._resource; };
1281
1282
1283 /**
1284  * Sets the node part of the jid
1285  * @param {String} node Name of the node
1286  * @throws JSJaCJIDInvalidException Thrown if node name contains invalid chars
1287  * @return This object
1288  * @type JSJaCJID
1289  */
1290 JSJaCJID.prototype.setNode = function(node) {
1291   JSJaCJID._checkNodeName(node);
1292   this._node = node || '';
1293   return this;
1294 };
1295
1296 /**
1297  * Sets the domain part of the jid
1298  * @param {String} domain Name of the domain
1299  * @throws JSJaCJIDInvalidException Thrown if domain name contains invalid
1300  * chars or is empty
1301  * @return This object
1302  * @type JSJaCJID
1303  */
1304 JSJaCJID.prototype.setDomain = function(domain) {
1305   if (!domain || domain == '')
1306     throw new JSJaCJIDInvalidException("domain name missing");
1307   // chars forbidden for a node are not allowed in domain names
1308   // anyway, so let's check
1309   JSJaCJID._checkNodeName(domain);
1310   this._domain = domain;
1311   return this;
1312 };
1313
1314 /**
1315  * Sets the resource part of the jid
1316  * @param {String} resource Name of the resource
1317  * @return This object
1318  * @type JSJaCJID
1319  */
1320 JSJaCJID.prototype.setResource = function(resource) {
1321   this._resource = resource || '';
1322   return this;
1323 };
1324
1325 /**
1326  * The string representation of the full jid
1327  * @return A string representing the jid
1328  * @type String
1329  */
1330 JSJaCJID.prototype.toString = function() {
1331   var jid = '';
1332   if (this.getNode() && this.getNode() != '')
1333     jid = this.getNode() + '@';
1334   jid += this.getDomain(); // we always have a domain
1335   if (this.getResource() && this.getResource() != "")
1336     jid += '/' + this.getResource();
1337   return jid;
1338 };
1339
1340 /**
1341  * Removes the resource part of the jid
1342  * @return This object
1343  * @type JSJaCJID
1344  */
1345 JSJaCJID.prototype.removeResource = function() {
1346   return this.setResource();
1347 };
1348
1349 /**
1350  * creates a copy of this JSJaCJID object
1351  * @return A copy of this
1352  * @type JSJaCJID
1353  */
1354 JSJaCJID.prototype.clone = function() {
1355   return new JSJaCJID(this.toString());
1356 };
1357
1358 /**
1359  * Compares two jids if they belong to the same entity (i.e. w/o resource)
1360  * @param {String} jid a jid as string or JSJaCJID object
1361  * @return 'true' if jid is same entity as this
1362  * @type Boolean
1363  */
1364 JSJaCJID.prototype.isEntity = function(jid) {
1365   if (typeof jid == 'string')
1366           jid = (new JSJaCJID(jid));
1367   jid.removeResource();
1368   return (this.clone().removeResource().toString() === jid.toString());
1369 };
1370
1371 /**
1372  * Check if node name is valid
1373  * @private
1374  * @param {String} node A name for a node
1375  * @throws JSJaCJIDInvalidException Thrown if name for node is not allowed
1376  */
1377 JSJaCJID._checkNodeName = function(nodeprep) {
1378     if (!nodeprep || nodeprep == '')
1379       return;
1380     for (var i=0; i< JSJACJID_FORBIDDEN.length; i++) {
1381       if (nodeprep.indexOf(JSJACJID_FORBIDDEN[i]) != -1) {
1382         throw new JSJaCJIDInvalidException("forbidden char in nodename: "+JSJACJID_FORBIDDEN[i]);
1383       }
1384     }
1385 };
1386
1387 /**
1388  * Creates a new Exception of type JSJaCJIDInvalidException
1389  * @class Exception to indicate invalid values for a jid
1390  * @constructor
1391  * @param {String} message The message associated with this Exception
1392  */
1393 function JSJaCJIDInvalidException(message) {
1394   /**
1395    * The exceptions associated message
1396    * @type String
1397    */
1398   this.message = message;
1399   /**
1400    * The name of the exception
1401    * @type String
1402    */
1403   this.name = "JSJaCJIDInvalidException";
1404 }
1405
1406
1407 /* Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
1408  *
1409  * Permission is hereby granted, free of charge, to any person
1410  * obtaining a copy of this software and associated documentation
1411  * files (the "Software"), to deal in the Software without
1412  * restriction, including without limitation the rights to use, copy,
1413  * modify, merge, publish, distribute, sublicense, and/or sell copies
1414  * of the Software, and to permit persons to whom the Software is
1415  * furnished to do so, subject to the following conditions:
1416  *
1417  * The above copyright notice and this permission notice shall be
1418  * included in all copies or substantial portions of the Software.
1419  *
1420  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1421  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1422  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1423  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
1424  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
1425  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1426  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1427  * SOFTWARE.
1428  */
1429
1430 /**
1431  * @private
1432  * This code is taken from {@link
1433  * http://wiki.script.aculo.us/scriptaculous/show/Builder
1434  * script.aculo.us' Dom Builder} and has been modified to suit our
1435  * needs.<br/>
1436  * The original parts of the code do have the following
1437  * copyright and license notice:<br/>
1438  * Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us,
1439  * http://mir.acu lo.us) <br/>
1440  * script.aculo.us is freely distributable under the terms of an
1441  * MIT-style license.<br>
1442  * For details, see the script.aculo.us web site:
1443  * http://script.aculo.us/<br>
1444  */
1445 var JSJaCBuilder = {
1446   /**
1447    * @private
1448    */
1449   buildNode: function(doc, elementName) {
1450
1451     var element, ns = arguments[4];
1452
1453     // attributes (or text)
1454     if(arguments[2])
1455       if(JSJaCBuilder._isStringOrNumber(arguments[2]) ||
1456          (arguments[2] instanceof Array)) {
1457         element = this._createElement(doc, elementName, ns);
1458         JSJaCBuilder._children(doc, element, arguments[2]);
1459       } else {
1460         ns = arguments[2]['xmlns'] || ns;
1461         element = this._createElement(doc, elementName, ns);
1462         for(attr in arguments[2]) {
1463           if (arguments[2].hasOwnProperty(attr) && attr != 'xmlns')
1464             element.setAttribute(attr, arguments[2][attr]);
1465         }
1466       }
1467     else
1468       element = this._createElement(doc, elementName, ns);
1469     // text, or array of children
1470     if(arguments[3])
1471       JSJaCBuilder._children(doc, element, arguments[3], ns);
1472
1473     return element;
1474   },
1475
1476   _createElement: function(doc, elementName, ns) {
1477     try {
1478       if (ns)
1479         return doc.createElementNS(ns, elementName);
1480     } catch (ex) { }
1481
1482     var el = doc.createElement(elementName);
1483
1484     if (ns)
1485       el.setAttribute("xmlns", ns);
1486
1487     return el;
1488   },
1489
1490   /**
1491    * @private
1492    */
1493   _text: function(doc, text) {
1494     return doc.createTextNode(text);
1495   },
1496
1497   /**
1498    * @private
1499    */
1500   _children: function(doc, element, children, ns) {
1501     if(typeof children=='object') { // array can hold nodes and text
1502       for (var i in children) {
1503         if (children.hasOwnProperty(i)) {
1504           var e = children[i];
1505           if (typeof e=='object') {
1506             if (e instanceof Array) {
1507               var node = JSJaCBuilder.buildNode(doc, e[0], e[1], e[2], ns);
1508               element.appendChild(node);
1509             } else {
1510               element.appendChild(e);
1511             }
1512           } else {
1513             if(JSJaCBuilder._isStringOrNumber(e)) {
1514               element.appendChild(JSJaCBuilder._text(doc, e));
1515             }
1516           }
1517         }
1518       }
1519     } else {
1520       if(JSJaCBuilder._isStringOrNumber(children)) {
1521         element.appendChild(JSJaCBuilder._text(doc, children));
1522       }
1523     }
1524   },
1525
1526   _attributes: function(attributes) {
1527     var attrs = [];
1528     for(attribute in attributes)
1529       if (attributes.hasOwnProperty(attribute))
1530         attrs.push(attribute +
1531           '="' + attributes[attribute].toString().htmlEnc() + '"');
1532     return attrs.join(" ");
1533   },
1534
1535   _isStringOrNumber: function(param) {
1536     return(typeof param=='string' || typeof param=='number');
1537   }
1538 };
1539
1540
1541 /**
1542  * @fileoverview Contains all Jabber/XMPP packet related classes.
1543  * @author Stefan Strigler steve@zeank.in-berlin.de
1544  * @version $Revision$
1545  */
1546
1547 var JSJACPACKET_USE_XMLNS = true;
1548
1549 /**
1550  * Creates a new packet with given root tag name (for internal use)
1551  * @class Somewhat abstract base class for all kinds of specialised packets
1552  * @param {String} name The root tag name of the packet
1553  * (i.e. one of 'message', 'iq' or 'presence')
1554  */
1555 function JSJaCPacket(name) {
1556   /**
1557    * @private
1558    */
1559   this.name = name;
1560
1561   if (typeof(JSJACPACKET_USE_XMLNS) != 'undefined' && JSJACPACKET_USE_XMLNS)
1562     /**
1563      * @private
1564      */
1565     this.doc = XmlDocument.create(name,'jabber:client');
1566   else
1567     /**
1568      * @private
1569      */
1570     this.doc = XmlDocument.create(name,'');
1571 }
1572
1573 /**
1574  * Gets the type (name of root element) of this packet, i.e. one of
1575  * 'presence', 'message' or 'iq'
1576  * @return the top level tag name
1577  * @type String
1578  */
1579 JSJaCPacket.prototype.pType = function() { return this.name; };
1580
1581 /**
1582  * Gets the associated Document for this packet.
1583  * @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#i-Document Document}
1584  */
1585 JSJaCPacket.prototype.getDoc = function() {
1586   return this.doc;
1587 };
1588 /**
1589  * Gets the root node of this packet
1590  * @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
1591  */
1592 JSJaCPacket.prototype.getNode = function() {
1593   if (this.getDoc() && this.getDoc().documentElement)
1594     return this.getDoc().documentElement;
1595   else
1596     return null;
1597 };
1598
1599 /**
1600  * Sets the 'to' attribute of the root node of this packet
1601  * @param {String} to
1602  * @type JSJaCPacket
1603  */
1604 JSJaCPacket.prototype.setTo = function(to) {
1605   if (!to || to == '')
1606     this.getNode().removeAttribute('to');
1607   else if (typeof(to) == 'string')
1608     this.getNode().setAttribute('to',to);
1609   else
1610     this.getNode().setAttribute('to',to.toString());
1611   return this;
1612 };
1613 /**
1614  * Sets the 'from' attribute of the root node of this
1615  * packet. Usually this is not needed as the server will take care
1616  * of this automatically.
1617  * @type JSJaCPacket
1618  */
1619 JSJaCPacket.prototype.setFrom = function(from) {
1620   if (!from || from == '')
1621     this.getNode().removeAttribute('from');
1622   else if (typeof(from) == 'string')
1623     this.getNode().setAttribute('from',from);
1624   else
1625     this.getNode().setAttribute('from',from.toString());
1626   return this;
1627 };
1628 /**
1629  * Sets 'id' attribute of the root node of this packet.
1630  * @param {String} id The id of the packet.
1631  * @type JSJaCPacket
1632  */
1633 JSJaCPacket.prototype.setID = function(id) {
1634   if (!id || id == '')
1635     this.getNode().removeAttribute('id');
1636   else
1637     this.getNode().setAttribute('id',id);
1638   return this;
1639 };
1640 /**
1641  * Sets the 'type' attribute of the root node of this packet.
1642  * @param {String} type The type of the packet.
1643  * @type JSJaCPacket
1644  */
1645 JSJaCPacket.prototype.setType = function(type) {
1646   if (!type || type == '')
1647     this.getNode().removeAttribute('type');
1648   else
1649     this.getNode().setAttribute('type',type);
1650   return this;
1651 };
1652 /**
1653  * Sets 'xml:lang' for this packet
1654  * @param {String} xmllang The xml:lang of the packet.
1655  * @type JSJaCPacket
1656  */
1657 JSJaCPacket.prototype.setXMLLang = function(xmllang) {
1658   // Fix IE9+ bug with xml:lang attribute
1659   if (BrowserDetect && (BrowserDetect.browser == 'Explorer') && (BrowserDetect.version >= 9))
1660     return this;
1661   if (!xmllang || xmllang == '')
1662     this.getNode().removeAttribute('xml:lang');
1663   else
1664     this.getNode().setAttribute('xml:lang',xmllang);
1665   return this;
1666 };
1667
1668 /**
1669  * Gets the 'to' attribute of this packet
1670  * @type String
1671  */
1672 JSJaCPacket.prototype.getTo = function() {
1673   return this.getNode().getAttribute('to');
1674 };
1675 /**
1676  * Gets the 'from' attribute of this packet.
1677  * @type String
1678  */
1679 JSJaCPacket.prototype.getFrom = function() {
1680   return this.getNode().getAttribute('from');
1681 };
1682 /**
1683  * Gets the 'to' attribute of this packet as a JSJaCJID object
1684  * @type JSJaCJID
1685  */
1686 JSJaCPacket.prototype.getToJID = function() {
1687   return new JSJaCJID(this.getTo());
1688 };
1689 /**
1690  * Gets the 'from' attribute of this packet as a JSJaCJID object
1691  * @type JSJaCJID
1692  */
1693 JSJaCPacket.prototype.getFromJID = function() {
1694   return new JSJaCJID(this.getFrom());
1695 };
1696 /**
1697  * Gets the 'id' of this packet
1698  * @type String
1699  */
1700 JSJaCPacket.prototype.getID = function() {
1701   return this.getNode().getAttribute('id');
1702 };
1703 /**
1704  * Gets the 'type' of this packet
1705  * @type String
1706  */
1707 JSJaCPacket.prototype.getType = function() {
1708   return this.getNode().getAttribute('type');
1709 };
1710 /**
1711  * Gets the 'xml:lang' of this packet
1712  * @type String
1713  */
1714 JSJaCPacket.prototype.getXMLLang = function() {
1715   return this.getNode().getAttribute('xml:lang');
1716 };
1717 /**
1718  * Gets the 'xmlns' (xml namespace) of the root node of this packet
1719  * @type String
1720  */
1721 JSJaCPacket.prototype.getXMLNS = function() {
1722   return this.getNode().namespaceURI || this.getNode().getAttribute('xmlns');
1723 };
1724
1725 /**
1726  * Gets a child element of this packet. If no params given returns first child.
1727  * @param {String} name Tagname of child to retrieve. Use '*' to match any tag. [optional]
1728  * @param {String} ns   Namespace of child. Use '*' to match any ns.[optional]
1729  * @return The child node, null if none found
1730  * @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
1731  */
1732 JSJaCPacket.prototype.getChild = function(name, ns) {
1733   if (!this.getNode()) {
1734     return null;
1735   }
1736
1737   name = name || '*';
1738   ns = ns || '*';
1739
1740   if (this.getNode().getElementsByTagNameNS) {
1741     return this.getNode().getElementsByTagNameNS(ns, name).item(0);
1742   }
1743
1744   // fallback
1745   var nodes = this.getNode().getElementsByTagName(name);
1746   if (ns != '*') {
1747     for (var i=0; i<nodes.length; i++) {
1748       if (nodes.item(i).namespaceURI == ns || nodes.item(i).getAttribute('xmlns') == ns) {
1749         return nodes.item(i);
1750       }
1751     }
1752   } else {
1753     return nodes.item(0);
1754   }
1755   return null; // nothing found
1756 };
1757
1758 /**
1759  * Gets the node value of a child element of this packet.
1760  * @param {String} name Tagname of child to retrieve.
1761  * @param {String} ns   Namespace of child
1762  * @return The value of the child node, empty string if none found
1763  * @type String
1764  */
1765 JSJaCPacket.prototype.getChildVal = function(name, ns) {
1766   var node = this.getChild(name, ns);
1767   var ret = '';
1768   if (node && node.hasChildNodes()) {
1769     // concatenate all values from childNodes
1770     for (var i=0; i<node.childNodes.length; i++)
1771       if (node.childNodes.item(i).nodeValue)
1772         ret += node.childNodes.item(i).nodeValue;
1773   }
1774   return ret;
1775 };
1776
1777 /**
1778  * Returns a copy of this node
1779  * @return a copy of this node
1780  * @type JSJaCPacket
1781  */
1782 JSJaCPacket.prototype.clone = function() {
1783   return JSJaCPacket.wrapNode(this.getNode());
1784 };
1785
1786 /**
1787  * Checks if packet is of type 'error'
1788  * @return 'true' if this packet is of type 'error', 'false' otherwise
1789  * @type boolean
1790  */
1791 JSJaCPacket.prototype.isError = function() {
1792   return (this.getType() == 'error');
1793 };
1794
1795 /**
1796  * Returns an error condition reply according to {@link http://www.xmpp.org/extensions/xep-0086.html XEP-0086}. Creates a clone of the calling packet with senders and recipient exchanged and error stanza appended.
1797  * @param {STANZA_ERROR} stanza_error an error stanza containing error cody, type and condition of the error to be indicated
1798  * @return an error reply packet
1799  * @type JSJaCPacket
1800  */
1801 JSJaCPacket.prototype.errorReply = function(stanza_error) {
1802   var rPacket = this.clone();
1803   rPacket.setTo(this.getFrom());
1804   rPacket.setFrom();
1805   rPacket.setType('error');
1806
1807   rPacket.appendNode('error',
1808                      {code: stanza_error.code, type: stanza_error.type},
1809                      [[stanza_error.cond]]);
1810
1811   return rPacket;
1812 };
1813
1814 /**
1815  * Returns a string representation of the raw xml content of this packet.
1816  * @type String
1817  */
1818 JSJaCPacket.prototype.xml = typeof XMLSerializer != 'undefined' ?
1819 function() {
1820   var r = (new XMLSerializer()).serializeToString(this.getNode());
1821   if (typeof(r) == 'undefined')
1822     r = (new XMLSerializer()).serializeToString(this.doc); // oldschool
1823   return r
1824 } :
1825 function() {// IE
1826   return this.getDoc().xml
1827 };
1828
1829
1830 // PRIVATE METHODS DOWN HERE
1831
1832 /**
1833  * Gets an attribute of the root element
1834  * @private
1835  */
1836 JSJaCPacket.prototype._getAttribute = function(attr) {
1837   return this.getNode().getAttribute(attr);
1838 };
1839
1840
1841 if (document.ELEMENT_NODE == null) {
1842   document.ELEMENT_NODE = 1;
1843   document.ATTRIBUTE_NODE = 2;
1844   document.TEXT_NODE = 3;
1845   document.CDATA_SECTION_NODE = 4;
1846   document.ENTITY_REFERENCE_NODE = 5;
1847   document.ENTITY_NODE = 6;
1848   document.PROCESSING_INSTRUCTION_NODE = 7;
1849   document.COMMENT_NODE = 8;
1850   document.DOCUMENT_NODE = 9;
1851   document.DOCUMENT_TYPE_NODE = 10;
1852   document.DOCUMENT_FRAGMENT_NODE = 11;
1853   document.NOTATION_NODE = 12;
1854 }
1855
1856 /**
1857  * import node into this packets document
1858  * @private
1859  */
1860 JSJaCPacket.prototype._importNode = function(node, allChildren) {
1861   switch (node.nodeType) {
1862   case document.ELEMENT_NODE:
1863
1864   if (this.getDoc().createElementNS) {
1865     var newNode = this.getDoc().createElementNS(node.namespaceURI, node.nodeName);
1866   } else {
1867     var newNode = this.getDoc().createElement(node.nodeName);
1868   }
1869
1870   /* does the node have any attributes to add? */
1871   if (node.attributes && node.attributes.length > 0)
1872     for (var i = 0, il = node.attributes.length;i < il; i++) {
1873       var attr = node.attributes.item(i);
1874       if (attr.nodeName == 'xmlns' && newNode.getAttribute('xmlns') != null ) continue;
1875       if (newNode.setAttributeNS && attr.namespaceURI) {
1876         newNode.setAttributeNS(attr.namespaceURI,
1877                                attr.nodeName,
1878                                attr.nodeValue);
1879       } else {
1880         newNode.setAttribute(attr.nodeName,
1881                              attr.nodeValue);
1882       }
1883     }
1884   /* are we going after children too, and does the node have any? */
1885   if (allChildren && node.childNodes && node.childNodes.length > 0) {
1886     for (var i = 0, il = node.childNodes.length; i < il; i++) {
1887       newNode.appendChild(this._importNode(node.childNodes.item(i), allChildren));
1888     }
1889   }
1890   return newNode;
1891   break;
1892   case document.TEXT_NODE:
1893   case document.CDATA_SECTION_NODE:
1894   case document.COMMENT_NODE:
1895   return this.getDoc().createTextNode(node.nodeValue);
1896   break;
1897   }
1898 };
1899
1900 /**
1901  * Set node value of a child node
1902  * @private
1903  */
1904 JSJaCPacket.prototype._setChildNode = function(nodeName, nodeValue) {
1905   var aNode = this.getChild(nodeName);
1906   var tNode = this.getDoc().createTextNode(nodeValue);
1907   if (aNode)
1908     try {
1909       aNode.replaceChild(tNode,aNode.firstChild);
1910     } catch (e) { }
1911   else {
1912     try {
1913       aNode = this.getDoc().createElementNS(this.getNode().namespaceURI,
1914                                             nodeName);
1915     } catch (ex) {
1916       aNode = this.getDoc().createElement(nodeName)
1917     }
1918     this.getNode().appendChild(aNode);
1919     aNode.appendChild(tNode);
1920   }
1921   return aNode;
1922 };
1923
1924 /**
1925  * Builds a node using {@link
1926  * http://wiki.script.aculo.us/scriptaculous/show/Builder
1927  * script.aculo.us' Dom Builder} notation.
1928  * This code is taken from {@link
1929  * http://wiki.script.aculo.us/scriptaculous/show/Builder
1930  * script.aculo.us' Dom Builder} and has been modified to suit our
1931  * needs.<br/>
1932  * The original parts of the code do have the following copyright
1933  * and license notice:<br/>
1934  * Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us,
1935  * http://mir.acu lo.us) <br/>
1936  * script.aculo.us is freely distributable under the terms of an
1937  * MIT-style licen se.  // For details, see the script.aculo.us web
1938  * site: http://script.aculo.us/<br>
1939  * @author Thomas Fuchs
1940  * @author Stefan Strigler
1941  * @return The newly created node
1942  * @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
1943  */
1944 JSJaCPacket.prototype.buildNode = function(elementName) {
1945   return JSJaCBuilder.buildNode(this.getDoc(),
1946                                 elementName,
1947                                 arguments[1],
1948                                 arguments[2]);
1949 };
1950
1951 /**
1952  * Appends node created by buildNode to this packets parent node.
1953  * @param {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node} element The node to append or
1954  * @param {String} element A name plus an object hash with attributes (optional) plus an array of childnodes (optional)
1955  * @see #buildNode
1956  * @return This packet
1957  * @type JSJaCPacket
1958  */
1959 JSJaCPacket.prototype.appendNode = function(element) {
1960   if (typeof element=='object') { // seems to be a prebuilt node
1961     return this.getNode().appendChild(element)
1962   } else { // build node
1963     return this.getNode().appendChild(this.buildNode(element,
1964                                                      arguments[1],
1965                                                      arguments[2],
1966                                                      null,
1967                                                      this.getNode().namespaceURI));
1968   }
1969 };
1970
1971
1972 /**
1973  * A jabber/XMPP presence packet
1974  * @class Models the XMPP notion of a 'presence' packet
1975  * @extends JSJaCPacket
1976  */
1977 function JSJaCPresence() {
1978   /**
1979    * @ignore
1980    */
1981   this.base = JSJaCPacket;
1982   this.base('presence');
1983 }
1984 JSJaCPresence.prototype = new JSJaCPacket;
1985
1986 /**
1987  * Sets the status message for current status. Usually this is set
1988  * to some human readable string indicating what the user is
1989  * doing/feel like currently.
1990  * @param {String} status A status message
1991  * @return this
1992  * @type JSJaCPacket
1993  */
1994 JSJaCPresence.prototype.setStatus = function(status) {
1995   this._setChildNode("status", status);
1996   return this;
1997 };
1998 /**
1999  * Sets the online status for this presence packet.
2000  * @param {String} show An XMPP complient status indicator. Must
2001  * be one of 'chat', 'away', 'xa', 'dnd'
2002  * @return this
2003  * @type JSJaCPacket
2004  */
2005 JSJaCPresence.prototype.setShow = function(show) {
2006   if (show == 'chat' || show == 'away' || show == 'xa' || show == 'dnd')
2007     this._setChildNode("show",show);
2008   return this;
2009 };
2010 /**
2011  * Sets the priority of the resource bind to with this connection
2012  * @param {int} prio The priority to set this resource to
2013  * @return this
2014  * @type JSJaCPacket
2015  */
2016 JSJaCPresence.prototype.setPriority = function(prio) {
2017   this._setChildNode("priority", prio);
2018   return this;
2019 };
2020 /**
2021  * Some combined method that allowes for setting show, status and
2022  * priority at once
2023  * @param {String} show A status message
2024  * @param {String} status A status indicator as defined by XMPP
2025  * @param {int} prio A priority for this resource
2026  * @return this
2027  * @type JSJaCPacket
2028  */
2029 JSJaCPresence.prototype.setPresence = function(show,status,prio) {
2030   if (show)
2031     this.setShow(show);
2032   if (status)
2033     this.setStatus(status);
2034   if (prio)
2035     this.setPriority(prio);
2036   return this;
2037 };
2038
2039 /**
2040  * Gets the status message of this presence
2041  * @return The (human readable) status message
2042  * @type String
2043  */
2044 JSJaCPresence.prototype.getStatus = function() {
2045   return this.getChildVal('status');
2046 };
2047 /**
2048  * Gets the status of this presence.
2049  * Either one of 'chat', 'away', 'xa' or 'dnd' or null.
2050  * @return The status indicator as defined by XMPP
2051  * @type String
2052  */
2053 JSJaCPresence.prototype.getShow = function() {
2054   return this.getChildVal('show');
2055 };
2056 /**
2057  * Gets the priority of this status message
2058  * @return A resource priority
2059  * @type int
2060  */
2061 JSJaCPresence.prototype.getPriority = function() {
2062   return this.getChildVal('priority');
2063 };
2064
2065
2066 /**
2067  * A jabber/XMPP iq packet
2068  * @class Models the XMPP notion of an 'iq' packet
2069  * @extends JSJaCPacket
2070  */
2071 function JSJaCIQ() {
2072   /**
2073    * @ignore
2074    */
2075   this.base = JSJaCPacket;
2076   this.base('iq');
2077 }
2078 JSJaCIQ.prototype = new JSJaCPacket;
2079
2080 /**
2081  * Some combined method to set 'to', 'type' and 'id' at once
2082  * @param {String} to the recepients JID
2083  * @param {String} type A XMPP compliant iq type (one of 'set', 'get', 'result' and 'error'
2084  * @param {String} id A packet ID
2085  * @return this
2086  * @type JSJaCIQ
2087  */
2088 JSJaCIQ.prototype.setIQ = function(to,type,id) {
2089   if (to)
2090     this.setTo(to);
2091   if (type)
2092     this.setType(type);
2093   if (id)
2094     this.setID(id);
2095   return this;
2096 };
2097 /**
2098  * Creates a 'query' child node with given XMLNS
2099  * @param {String} xmlns The namespace for the 'query' node
2100  * @return The query node
2101  * @type {@link  http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
2102  */
2103 JSJaCIQ.prototype.setQuery = function(xmlns) {
2104   var query;
2105   try {
2106     query = this.getDoc().createElementNS(xmlns,'query');
2107   } catch (e) {
2108     query = this.getDoc().createElement('query');
2109         query.setAttribute('xmlns',xmlns);
2110   }
2111   this.getNode().appendChild(query);
2112   return query;
2113 };
2114
2115 /**
2116  * Gets the 'query' node of this packet
2117  * @return The query node
2118  * @type {@link  http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
2119  */
2120 JSJaCIQ.prototype.getQuery = function() {
2121   return this.getNode().getElementsByTagName('query').item(0);
2122 };
2123 /**
2124  * Gets the XMLNS of the query node contained within this packet
2125  * @return The namespace of the query node
2126  * @type String
2127  */
2128 JSJaCIQ.prototype.getQueryXMLNS = function() {
2129   if (this.getQuery()) {
2130     return this.getQuery().namespaceURI || this.getQuery().getAttribute('xmlns');
2131   } else {
2132     return null;
2133   }
2134 };
2135
2136 /**
2137  * Creates an IQ reply with type set to 'result'. If given appends payload to first child if IQ. Payload maybe XML as string or a DOM element (or an array of such elements as well).
2138  * @param {Element} payload A payload to be appended [optional]
2139  * @return An IQ reply packet
2140  * @type JSJaCIQ
2141  */
2142 JSJaCIQ.prototype.reply = function(payload) {
2143   var rIQ = this.clone();
2144   rIQ.setTo(this.getFrom());
2145   rIQ.setFrom();
2146   rIQ.setType('result');
2147   if (payload) {
2148     if (typeof payload == 'string')
2149       rIQ.getChild().appendChild(rIQ.getDoc().loadXML(payload));
2150     else if (payload.constructor == Array) {
2151       var node = rIQ.getChild();
2152       for (var i=0; i<payload.length; i++)
2153         if(typeof payload[i] == 'string')
2154           node.appendChild(rIQ.getDoc().loadXML(payload[i]));
2155         else if (typeof payload[i] == 'object')
2156           node.appendChild(payload[i]);
2157     }
2158     else if (typeof payload == 'object')
2159       rIQ.getChild().appendChild(payload);
2160   }
2161   return rIQ;
2162 };
2163
2164 /**
2165  * A jabber/XMPP message packet
2166  * @class Models the XMPP notion of an 'message' packet
2167  * @extends JSJaCPacket
2168  */
2169 function JSJaCMessage() {
2170   /**
2171    * @ignore
2172    */
2173   this.base = JSJaCPacket;
2174   this.base('message');
2175 }
2176 JSJaCMessage.prototype = new JSJaCPacket;
2177
2178 /**
2179  * Sets the body of the message
2180  * @param {String} body Your message to be sent along
2181  * @return this message
2182  * @type JSJaCMessage
2183  */
2184 JSJaCMessage.prototype.setBody = function(body) {
2185   this._setChildNode("body",body);
2186   return this;
2187 };
2188 /**
2189  * Sets the subject of the message
2190  * @param {String} subject Your subject to be sent along
2191  * @return this message
2192  * @type JSJaCMessage
2193  */
2194 JSJaCMessage.prototype.setSubject = function(subject) {
2195   this._setChildNode("subject",subject);
2196   return this;
2197 };
2198 /**
2199  * Sets the 'tread' attribute for this message. This is used to identify
2200  * threads in chat conversations
2201  * @param {String} thread Usually a somewhat random hash.
2202  * @return this message
2203  * @type JSJaCMessage
2204  */
2205 JSJaCMessage.prototype.setThread = function(thread) {
2206   this._setChildNode("thread", thread);
2207   return this;
2208 };
2209 /**
2210  * Gets the 'thread' identifier for this message
2211  * @return A thread identifier
2212  * @type String
2213  */
2214 JSJaCMessage.prototype.getThread = function() {
2215   return this.getChildVal('thread');
2216 };
2217 /**
2218  * Gets the body of this message
2219  * @return The body of this message
2220  * @type String
2221  */
2222 JSJaCMessage.prototype.getBody = function() {
2223   return this.getChildVal('body');
2224 };
2225 /**
2226  * Gets the subject of this message
2227  * @return The subject of this message
2228  * @type String
2229  */
2230 JSJaCMessage.prototype.getSubject = function() {
2231   return this.getChildVal('subject')
2232 };
2233
2234
2235 /**
2236  * Tries to transform a w3c DOM node to JSJaC's internal representation
2237  * (JSJaCPacket type, one of JSJaCPresence, JSJaCMessage, JSJaCIQ)
2238  * @param: {Node
2239  * http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247}
2240  * node The node to be transformed
2241  * @return A JSJaCPacket representing the given node. If node's root
2242  * elemenent is not one of 'message', 'presence' or 'iq',
2243  * <code>null</code> is being returned.
2244  * @type JSJaCPacket
2245  */
2246 JSJaCPacket.wrapNode = function(node) {
2247   var oPacket = null;
2248
2249   switch (node.nodeName.toLowerCase()) {
2250   case 'presence':
2251       oPacket = new JSJaCPresence();
2252       break;
2253   case 'message':
2254       oPacket = new JSJaCMessage();
2255       break;
2256   case 'iq':
2257       oPacket = new JSJaCIQ();
2258       break;
2259   }
2260
2261   if (oPacket) {
2262     oPacket.getDoc().replaceChild(oPacket._importNode(node, true),
2263                                   oPacket.getNode());
2264   }
2265
2266   return oPacket;
2267 };
2268
2269
2270
2271 /**
2272  * an error packet for internal use
2273  * @private
2274  * @constructor
2275  */
2276 function JSJaCError(code,type,condition) {
2277   var xmldoc = XmlDocument.create("error","jsjac");
2278
2279   xmldoc.documentElement.setAttribute('code',code);
2280   xmldoc.documentElement.setAttribute('type',type);
2281   if (condition)
2282     xmldoc.documentElement.appendChild(xmldoc.createElement(condition)).
2283       setAttribute('xmlns','urn:ietf:params:xml:ns:xmpp-stanzas');
2284   return xmldoc.documentElement;
2285 }
2286
2287
2288
2289 /**
2290  * Creates a new set of hash keys
2291  * @class Reflects a set of sha1/md5 hash keys for securing sessions
2292  * @constructor
2293  * @param {Function} func The hash function to be used for creating the keys
2294  * @param {Debugger} oDbg Reference to debugger implementation [optional]
2295  */                                                                      
2296 function JSJaCKeys(func,oDbg) {
2297   var seed = Math.random();
2298
2299   /**
2300    * @private
2301    */
2302   this._k = new Array();
2303   this._k[0] = seed.toString();
2304   if (oDbg)
2305     /**
2306      * Reference to Debugger
2307      * @type Debugger
2308      */
2309     this.oDbg = oDbg;
2310   else {
2311     this.oDbg = {};
2312     this.oDbg.log = function() {};
2313   }
2314
2315   if (func) {
2316     for (var i=1; i<JSJAC_NKEYS; i++) {
2317       this._k[i] = func(this._k[i-1]);
2318       oDbg.log(i+": "+this._k[i],4);
2319     }
2320   }
2321
2322   /**
2323    * @private
2324    */
2325   this._indexAt = JSJAC_NKEYS-1;
2326   /**
2327    * Gets next key from stack
2328    * @return New hash key
2329    * @type String
2330    */
2331   this.getKey = function() {
2332     return this._k[this._indexAt--];
2333   };
2334   /**
2335    * Indicates whether there's only one key left
2336    * @return <code>true</code> if there's only one key left, false otherwise
2337    * @type boolean
2338    */
2339   this.lastKey = function() { return (this._indexAt == 0); };
2340   /**
2341    * Returns number of overall/initial stack size
2342    * @return Number of keys created
2343    * @type int
2344    */
2345   this.size = function() { return this._k.length; };
2346
2347   /**
2348    * @private
2349    */
2350   this._getSuspendVars = function() {
2351     return ('_k,_indexAt').split(',');
2352   }
2353 }
2354
2355
2356 /**
2357  * @fileoverview Contains all things in common for all subtypes of connections
2358  * supported.
2359  * @author Stefan Strigler steve@zeank.in-berlin.de
2360  * @version $Revision$
2361  */
2362
2363 /**
2364  * Creates a new Jabber connection (a connection to a jabber server)
2365  * @class Somewhat abstract base class for jabber connections. Contains all
2366  * of the code in common for all jabber connections
2367  * @constructor
2368  * @param {JSON http://www.json.org/index} oArg JSON with properties: <br>
2369  * * <code>httpbase</code> the http base address of the service to be used for
2370  * connecting to jabber<br>
2371  * * <code>oDbg</code> (optional) a reference to a debugger interface
2372  */
2373 function JSJaCConnection(oArg) {
2374
2375   if (oArg && oArg.oDbg && oArg.oDbg.log) {
2376       /**
2377        * Reference to debugger interface
2378        * (needs to implement method <code>log</code>)
2379        * @type Debugger
2380        */
2381     this.oDbg = oArg.oDbg;
2382   } else {
2383     this.oDbg = new Object(); // always initialise a debugger
2384     this.oDbg.log = function() { };
2385   }
2386
2387   if (oArg && oArg.timerval)
2388     this.setPollInterval(oArg.timerval);
2389   else
2390     this.setPollInterval(JSJAC_TIMERVAL);
2391
2392   if (oArg && oArg.httpbase)
2393       /**
2394        * @private
2395        */
2396     this._httpbase = oArg.httpbase;
2397
2398   if (oArg &&oArg.allow_plain)
2399       /**
2400        * @private
2401        */
2402     this.allow_plain = oArg.allow_plain;
2403   else
2404     this.allow_plain = JSJAC_ALLOW_PLAIN;
2405
2406   if (oArg && oArg.cookie_prefix)
2407       /**
2408        * @private
2409        */
2410     this._cookie_prefix = oArg.cookie_prefix;
2411   else
2412     this._cookie_prefix = "";
2413
2414   /**
2415    * @private
2416    */
2417   this._connected = false;
2418   /**
2419    * @private
2420    */
2421   this._events = new Array();
2422   /**
2423    * @private
2424    */
2425   this._keys = null;
2426   /**
2427    * @private
2428    */
2429   this._ID = 0;
2430   /**
2431    * @private
2432    */
2433   this._inQ = new Array();
2434   /**
2435    * @private
2436    */
2437   this._pQueue = new Array();
2438   /**
2439    * @private
2440    */
2441   this._regIDs = new Array();
2442   /**
2443    * @private
2444    */
2445   this._req = new Array();
2446   /**
2447    * @private
2448    */
2449   this._status = 'intialized';
2450   /**
2451    * @private
2452    */
2453   this._errcnt = 0;
2454   /**
2455    * @private
2456    */
2457   this._inactivity = JSJAC_INACTIVITY;
2458   /**
2459    * @private
2460    */
2461   this._sendRawCallbacks = new Array();
2462 }
2463
2464 // Generates an ID
2465 var STANZA_ID = 1;
2466
2467 function genID() {
2468   return STANZA_ID++;
2469 }
2470
2471 JSJaCConnection.prototype.connect = function(oArg) {
2472     this._setStatus('connecting');
2473
2474     this.domain = oArg.domain || 'localhost';
2475     this.username = oArg.username;
2476     this.resource = oArg.resource;
2477     this.pass = oArg.pass;
2478     this.register = oArg.register;
2479
2480     this.authhost = oArg.authhost || this.domain;
2481     this.authtype = oArg.authtype || 'sasl';
2482
2483     if (oArg.xmllang && oArg.xmllang != '')
2484         this._xmllang = oArg.xmllang;
2485     else
2486         this._xmllang = 'en';
2487
2488     this.host = oArg.host || this.domain;
2489     this.port = oArg.port || 5222;
2490     if (oArg.secure)
2491         this.secure = 'true';
2492     else
2493         this.secure = 'false';
2494
2495     if (oArg.wait)
2496         this._wait = oArg.wait;
2497
2498     this.jid = this.username + '@' + this.domain;
2499     this.fulljid = this.jid + '/' + this.resource;
2500
2501     this._rid  = Math.round( 100000.5 + ( ( (900000.49999) - (100000.5) ) * Math.random() ) );
2502
2503     // setupRequest must be done after rid is created but before first use in reqstr
2504     var slot = this._getFreeSlot();
2505     this._req[slot] = this._setupRequest(true);
2506
2507     var reqstr = this._getInitialRequestString();
2508
2509     this.oDbg.log(reqstr,4);
2510
2511     this._req[slot].r.onreadystatechange =
2512         JSJaC.bind(function() {
2513             var r = this._req[slot].r;
2514             if (r.readyState == 4) {
2515                 this.oDbg.log("async recv: "+r.responseText,4);
2516                 this._handleInitialResponse(r); // handle response
2517             }
2518         }, this);
2519
2520     if (typeof(this._req[slot].r.onerror) != 'undefined') {
2521         this._req[slot].r.onerror =
2522             JSJaC.bind(function(e) {
2523                 this.oDbg.log('XmlHttpRequest error',1);
2524                 return false;
2525             }, this);
2526     }
2527
2528     this._req[slot].r.send(reqstr);
2529 };
2530
2531 /**
2532  * Tells whether this connection is connected
2533  * @return <code>true</code> if this connections is connected,
2534  * <code>false</code> otherwise
2535  * @type boolean
2536  */
2537 JSJaCConnection.prototype.connected = function() { return this._connected; };
2538
2539 /**
2540  * Disconnects from jabber server and terminates session (if applicable)
2541  */
2542 JSJaCConnection.prototype.disconnect = function() {
2543   this._setStatus('disconnecting');
2544  
2545   if (!this.connected())
2546     return;
2547   this._connected = false;
2548  
2549   clearInterval(this._interval);
2550   clearInterval(this._inQto);
2551  
2552   if (this._timeout)
2553     clearTimeout(this._timeout); // remove timer
2554  
2555   var slot = this._getFreeSlot();
2556   // Intentionally synchronous
2557   this._req[slot] = this._setupRequest(false);
2558  
2559   request = this._getRequestString(false, true);
2560  
2561   this.oDbg.log("Disconnecting: " + request,4);
2562   this._req[slot].r.send(request);
2563  
2564   try {
2565     removeDB('jsjac', 'state');
2566   } catch (e) {}
2567  
2568   this.oDbg.log("Disconnected: "+this._req[slot].r.responseText,2);
2569   this._handleEvent('ondisconnect');
2570 };
2571
2572 /**
2573  * Gets current value of polling interval
2574  * @return Polling interval in milliseconds
2575  * @type int
2576  */
2577 JSJaCConnection.prototype.getPollInterval = function() {
2578   return this._timerval;
2579 };
2580
2581 /**
2582  * Registers an event handler (callback) for this connection.
2583
2584  * <p>Note: All of the packet handlers for specific packets (like
2585  * message_in, presence_in and iq_in) fire only if there's no
2586  * callback associated with the id.<br>
2587
2588  * <p>Example:<br/>
2589  * <code>con.registerHandler('iq', 'query', 'jabber:iq:version', handleIqVersion);</code>
2590
2591
2592  * @param {String} event One of
2593
2594  * <ul>
2595  * <li>onConnect - connection has been established and authenticated</li>
2596  * <li>onDisconnect - connection has been disconnected</li>
2597  * <li>onResume - connection has been resumed</li>
2598
2599  * <li>onStatusChanged - connection status has changed, current
2600  * status as being passed argument to handler. See {@link #status}.</li>
2601
2602  * <li>onError - an error has occured, error node is supplied as
2603  * argument, like this:<br><code>&lt;error code='404' type='cancel'&gt;<br>
2604  * &lt;item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/&gt;<br>
2605  * &lt;/error&gt;</code></li>
2606
2607  * <li>packet_in - a packet has been received (argument: the
2608  * packet)</li>
2609
2610  * <li>packet_out - a packet is to be sent(argument: the
2611  * packet)</li>
2612
2613  * <li>message_in | message - a message has been received (argument:
2614  * the packet)</li>
2615
2616  * <li>message_out - a message packet is to be sent (argument: the
2617  * packet)</li>
2618
2619  * <li>presence_in | presence - a presence has been received
2620  * (argument: the packet)</li>
2621
2622  * <li>presence_out - a presence packet is to be sent (argument: the
2623  * packet)</li>
2624
2625  * <li>iq_in | iq - an iq has been received (argument: the packet)</li>
2626  * <li>iq_out - an iq is to be sent (argument: the packet)</li>
2627  * </ul>
2628
2629  * @param {String} childName A childnode's name that must occur within a
2630  * retrieved packet [optional]
2631
2632  * @param {String} childNS A childnode's namespace that must occure within
2633  * a retrieved packet (works only if childName is given) [optional]
2634
2635  * @param {String} type The type of the packet to handle (works only if childName and chidNS are given (both may be set to '*' in order to get skipped) [optional]
2636
2637  * @param {Function} handler The handler to be called when event occurs. If your handler returns 'true' it cancels bubbling of the event. No other registered handlers for this event will be fired.
2638  */
2639 JSJaCConnection.prototype.registerHandler = function(event) {
2640   event = event.toLowerCase(); // don't be case-sensitive here
2641   var eArg = {handler: arguments[arguments.length-1],
2642               childName: '*',
2643               childNS: '*',
2644               type: '*'};
2645   if (arguments.length > 2)
2646     eArg.childName = arguments[1];
2647   if (arguments.length > 3)
2648     eArg.childNS = arguments[2];
2649   if (arguments.length > 4)
2650     eArg.type = arguments[3];
2651   if (!this._events[event])
2652     this._events[event] = new Array(eArg);
2653   else
2654     this._events[event] = this._events[event].concat(eArg);
2655
2656   // sort events in order how specific they match criterias thus using
2657   // wildcard patterns puts them back in queue when it comes to
2658   // bubbling the event
2659   this._events[event] =
2660   this._events[event].sort(function(a,b) {
2661     var aRank = 0;
2662     var bRank = 0;
2663     with (a) {
2664       if (type == '*')
2665         aRank++;
2666       if (childNS == '*')
2667         aRank++;
2668       if (childName == '*')
2669         aRank++;
2670     }
2671     with (b) {
2672       if (type == '*')
2673         bRank++;
2674       if (childNS == '*')
2675         bRank++;
2676       if (childName == '*')
2677         bRank++;
2678     }
2679     if (aRank > bRank)
2680       return 1;
2681     if (aRank < bRank)
2682       return -1;
2683     return 0;
2684   });
2685   this.oDbg.log("registered handler for event '"+event+"'",2);
2686 };
2687
2688 JSJaCConnection.prototype.unregisterHandler = function(event,handler) {
2689   event = event.toLowerCase(); // don't be case-sensitive here
2690
2691   if (!this._events[event])
2692     return;
2693
2694   var arr = this._events[event], res = new Array();
2695   for (var i=0; i<arr.length; i++)
2696     if (arr[i].handler != handler)
2697       res.push(arr[i]);
2698
2699   if (arr.length != res.length) {
2700     this._events[event] = res;
2701     this.oDbg.log("unregistered handler for event '"+event+"'",2);
2702   }
2703 };
2704
2705 /**
2706  * Register for iq packets of type 'get'.
2707  * @param {String} childName A childnode's name that must occur within a
2708  * retrieved packet
2709
2710  * @param {String} childNS A childnode's namespace that must occure within
2711  * a retrieved packet (works only if childName is given)
2712
2713  * @param {Function} handler The handler to be called when event occurs. If your handler returns 'true' it cancels bubbling of the event. No other registered handlers for this event will be fired.
2714  */
2715 JSJaCConnection.prototype.registerIQGet = function(childName, childNS, handler) {
2716   this.registerHandler('iq', childName, childNS, 'get', handler);
2717 };
2718
2719 /**
2720  * Register for iq packets of type 'set'.
2721  * @param {String} childName A childnode's name that must occur within a
2722  * retrieved packet
2723
2724  * @param {String} childNS A childnode's namespace that must occure within
2725  * a retrieved packet (works only if childName is given)
2726
2727  * @param {Function} handler The handler to be called when event occurs. If your handler returns 'true' it cancels bubbling of the event. No other registered handlers for this event will be fired.
2728  */
2729 JSJaCConnection.prototype.registerIQSet = function(childName, childNS, handler) {
2730   this.registerHandler('iq', childName, childNS, 'set', handler);
2731 };
2732
2733 /**
2734  * Resumes this connection from saved state (cookie)
2735  * @return Whether resume was successful
2736  * @type boolean
2737  */
2738 JSJaCConnection.prototype.resume = function() {
2739   try {
2740     var json = getDB('jsjac', 'state');
2741     this.oDbg.log('read cookie: '+json,2);
2742     removeDB('jsjac', 'state');
2743
2744     return this.resumeFromData(JSJaCJSON.parse(json));
2745   } catch (e) {}
2746   return false; // sth went wrong
2747 };
2748
2749 /**
2750  * Resumes BOSH connection from data
2751  * @param {Object} serialized jsjac state information
2752  * @return Whether resume was successful
2753  * @type boolean
2754  */
2755 JSJaCConnection.prototype.resumeFromData = function(data) {
2756   try {
2757     this._setStatus('resuming');
2758
2759     for (var i in data)
2760       if (data.hasOwnProperty(i))
2761         this[i] = data[i];
2762
2763     // copy keys - not being very generic here :-/
2764     if (this._keys) {
2765       this._keys2 = new JSJaCKeys();
2766       var u = this._keys2._getSuspendVars();
2767       for (var i=0; i<u.length; i++)
2768         this._keys2[u[i]] = this._keys[u[i]];
2769       this._keys = this._keys2;
2770     }
2771
2772     if (this._connected) {
2773       // don't poll too fast!
2774       this._handleEvent('onresume');
2775       setTimeout(JSJaC.bind(this._resume, this),this.getPollInterval());
2776       this._interval = setInterval(JSJaC.bind(this._checkQueue, this),
2777                                    JSJAC_CHECKQUEUEINTERVAL);
2778       this._inQto = setInterval(JSJaC.bind(this._checkInQ, this),
2779                                 JSJAC_CHECKINQUEUEINTERVAL);
2780     }
2781
2782     return (this._connected === true);
2783   } catch (e) {
2784     if (e.message)
2785       this.oDbg.log("Resume failed: "+e.message, 1);
2786     else
2787       this.oDbg.log("Resume failed: "+e, 1);
2788     return false;
2789   }
2790 };
2791
2792 /**
2793  * Sends a JSJaCPacket
2794  * @param {JSJaCPacket} packet  The packet to send
2795  * @param {Function}    cb      The callback to be called if there's a reply
2796  * to this packet (identified by id) [optional]
2797  * @param {Object}      arg     Arguments passed to the callback
2798  * (additionally to the packet received) [optional]
2799  * @return 'true' if sending was successfull, 'false' otherwise
2800  * @type boolean
2801  */
2802 JSJaCConnection.prototype.send = function(packet,cb,arg) {
2803   if (!packet || !packet.pType) {
2804     this.oDbg.log("no packet: "+packet, 1);
2805     return false;
2806   }
2807
2808   if (!this.connected())
2809     return false;
2810
2811   // generate an ID for the packet
2812   if (!packet.getID())
2813     packet.setID(genID());
2814
2815   // packet xml:lang
2816   if (!packet.getXMLLang())
2817     packet.setXMLLang(XML_LANG);
2818
2819   // remember id for response if callback present
2820   if (cb)
2821     this._registerPID(packet.getID(),cb,arg);
2822
2823   try {
2824     this._handleEvent(packet.pType()+'_out', packet);
2825     this._handleEvent("packet_out", packet);
2826     this._pQueue = this._pQueue.concat(packet.xml());
2827   } catch (e) {
2828     this.oDbg.log(e.toString(),1);
2829     return false;
2830   }
2831
2832   return true;
2833 };
2834
2835 /**
2836  * Sends an IQ packet. Has default handlers for each reply type.
2837  * Those maybe overriden by passing an appropriate handler.
2838  * @param {JSJaCIQPacket} iq - the iq packet to send
2839  * @param {Object} handlers - object with properties 'error_handler',
2840  *                            'result_handler' and 'default_handler'
2841  *                            with appropriate functions
2842  * @param {Object} arg - argument to handlers
2843  * @return 'true' if sending was successfull, 'false' otherwise
2844  * @type boolean
2845  */
2846 JSJaCConnection.prototype.sendIQ = function(iq, handlers, arg) {
2847   if (!iq || iq.pType() != 'iq') {
2848     return false;
2849   }
2850
2851   handlers = handlers || {};
2852     var error_handler = handlers.error_handler || JSJaC.bind(function(aIq) {
2853         this.oDbg.log(aIq.xml(), 1);
2854     }, this);
2855
2856     var result_handler = handlers.result_handler ||  JSJaC.bind(function(aIq) {
2857         this.oDbg.log(aIq.xml(), 2);
2858     }, this);
2859
2860   var iqHandler = function(aIq, arg) {
2861     switch (aIq.getType()) {
2862       case 'error':
2863       error_handler(aIq);
2864       break;
2865       case 'result':
2866       result_handler(aIq, arg);
2867       break;
2868     }
2869   };
2870   return this.send(iq, iqHandler, arg);
2871 };
2872
2873 /**
2874  * Sets polling interval for this connection
2875  * @param {int} millisecs Milliseconds to set timer to
2876  * @return effective interval this connection has been set to
2877  * @type int
2878  */
2879 JSJaCConnection.prototype.setPollInterval = function(timerval) {
2880   if (timerval && !isNaN(timerval))
2881     this._timerval = timerval;
2882   return this._timerval;
2883 };
2884
2885 /**
2886  * Returns current status of this connection
2887  * @return String to denote current state. One of
2888  * <ul>
2889  * <li>'initializing' ... well
2890  * <li>'connecting' if connect() was called
2891  * <li>'resuming' if resume() was called
2892  * <li>'processing' if it's about to operate as normal
2893  * <li>'onerror_fallback' if there was an error with the request object
2894  * <li>'protoerror_fallback' if there was an error at the http binding protocol flow (most likely that's where you interested in)
2895  * <li>'internal_server_error' in case of an internal server error
2896  * <li>'suspending' if suspend() is being called
2897  * <li>'aborted' if abort() was called
2898  * <li>'disconnecting' if disconnect() has been called
2899  * </ul>
2900  * @type String
2901  */
2902 JSJaCConnection.prototype.status = function() { return this._status; };
2903
2904 /**
2905  * Suspends this connection (saving state for later resume)
2906  * Saves state to cookie
2907  * @return Whether suspend (saving to cookie) was successful
2908  * @type boolean
2909  */
2910 JSJaCConnection.prototype.suspend = function(has_pause) {
2911   var data = this.suspendToData(has_pause);
2912   
2913   try {
2914     var c = setDB('jsjac', 'state', JSJaCJSON.toString(data));
2915     return c;
2916   } catch (e) {
2917     this.oDbg.log("Failed creating cookie '"+this._cookie_prefix+
2918                   "JSJaC_State': "+e.message,1);
2919   }
2920   return false;
2921 };
2922
2923 /**
2924  * Suspend connection and return serialized JSJaC connection state
2925  * @return JSJaC connection state object
2926  * @type Object
2927  */
2928 JSJaCConnection.prototype.suspendToData = function(has_pause) {
2929   
2930   // remove timers
2931   if(has_pause) {
2932     clearTimeout(this._timeout);
2933     clearInterval(this._interval);
2934     clearInterval(this._inQto);
2935
2936     this._suspend();
2937   }
2938   
2939   var u = ('_connected,_keys,_ID,_inQ,_pQueue,_regIDs,_errcnt,_inactivity,domain,username,resource,jid,fulljid,_sid,_httpbase,_timerval,_is_polling').split(',');
2940   u = u.concat(this._getSuspendVars());
2941   var s = new Object();
2942
2943   for (var i=0; i<u.length; i++) {
2944     if (!this[u[i]]) continue; // hu? skip these!
2945     if (this[u[i]]._getSuspendVars) {
2946       var uo = this[u[i]]._getSuspendVars();
2947       var o = new Object();
2948       for (var j=0; j<uo.length; j++)
2949         o[uo[j]] = this[u[i]][uo[j]];
2950     } else
2951       var o = this[u[i]];
2952
2953     s[u[i]] = o;
2954   }
2955   
2956   if(has_pause) {
2957     this._connected = false;
2958     this._setStatus('suspending');
2959   }
2960   
2961   return s;
2962 };
2963
2964 /**
2965  * @private
2966  */
2967 JSJaCConnection.prototype._abort = function() {
2968   clearTimeout(this._timeout); // remove timer
2969
2970   clearInterval(this._inQto);
2971   clearInterval(this._interval);
2972
2973   this._connected = false;
2974
2975   this._setStatus('aborted');
2976
2977   this.oDbg.log("Disconnected.",1);
2978   this._handleEvent('ondisconnect');
2979   this._handleEvent('onerror',
2980                     JSJaCError('500','cancel','service-unavailable'));
2981 };
2982
2983 /**
2984  * @private
2985  */
2986 JSJaCConnection.prototype._checkInQ = function() {
2987   for (var i=0; i<this._inQ.length && i<10; i++) {
2988     var item = this._inQ[0];
2989     this._inQ = this._inQ.slice(1,this._inQ.length);
2990     var packet = JSJaCPacket.wrapNode(item);
2991
2992     if (!packet)
2993       return;
2994
2995     this._handleEvent("packet_in", packet);
2996
2997     if (packet.pType && !this._handlePID(packet)) {
2998       this._handleEvent(packet.pType()+'_in',packet);
2999       this._handleEvent(packet.pType(),packet);
3000     }
3001   }
3002 };
3003
3004 /**
3005  * @private
3006  */
3007 JSJaCConnection.prototype._checkQueue = function() {
3008   if (this._pQueue.length != 0)
3009     this._process();
3010   return true;
3011 };
3012
3013 /**
3014  * @private
3015  */
3016 JSJaCConnection.prototype._doAuth = function() {
3017   if (this.has_sasl && this.authtype == 'nonsasl')
3018     this.oDbg.log("Warning: SASL present but not used", 1);
3019
3020   if (!this._doSASLAuth() &&
3021       !this._doLegacyAuth()) {
3022     this.oDbg.log("Auth failed for authtype "+this.authtype,1);
3023     this.disconnect();
3024     return false;
3025   }
3026   return true;
3027 };
3028
3029 /**
3030  * @private
3031  */
3032 JSJaCConnection.prototype._doInBandReg = function() {
3033   if (this.authtype == 'saslanon' || this.authtype == 'anonymous')
3034     return; // bullshit - no need to register if anonymous
3035
3036   /* ***
3037    * In-Band Registration see JEP-0077
3038    */
3039
3040   var iq = new JSJaCIQ();
3041   iq.setType('set');
3042   iq.setID('reg1');
3043   iq.appendNode("query", {xmlns: "jabber:iq:register"},
3044                 [["username", this.username],
3045                  ["password", this.pass]]);
3046
3047   this.send(iq,this._doInBandRegDone);
3048 };
3049
3050 /**
3051  * @private
3052  */
3053 JSJaCConnection.prototype._doInBandRegDone = function(iq) {
3054   if (iq && iq.getType() == 'error') { // we failed to register
3055     this.oDbg.log("registration failed for "+this.username,0);
3056     this._handleEvent('onerror',iq.getChild('error'));
3057     return;
3058   }
3059
3060   this.oDbg.log(this.username + " registered succesfully",0);
3061
3062   this._doAuth();
3063 };
3064
3065 /**
3066  * @private
3067  */
3068 JSJaCConnection.prototype._doLegacyAuth = function() {
3069   if (this.authtype != 'nonsasl' && this.authtype != 'anonymous')
3070     return false;
3071
3072   /* ***
3073    * Non-SASL Authentication as described in JEP-0078
3074    */
3075   var iq = new JSJaCIQ();
3076   iq.setIQ(null,'get','auth1');
3077   iq.appendNode('query', {xmlns: 'jabber:iq:auth'},
3078                 [['username', this.username]]);
3079
3080   this.send(iq,this._doLegacyAuth2);
3081   return true;
3082 };
3083
3084 /**
3085  * @private
3086  */
3087 JSJaCConnection.prototype._doLegacyAuth2 = function(iq) {
3088   if (!iq || iq.getType() != 'result') {
3089     if (iq && iq.getType() == 'error')
3090       this._handleEvent('onerror',iq.getChild('error'));
3091     this.disconnect();
3092     return;
3093   }
3094
3095   var use_digest = (iq.getChild('digest') != null);
3096
3097   /* ***
3098    * Send authentication
3099    */
3100   var iq = new JSJaCIQ();
3101   iq.setIQ(null,'set','auth2');
3102
3103   query = iq.appendNode('query', {xmlns: 'jabber:iq:auth'},
3104                         [['username', this.username],
3105                          ['resource', this.resource]]);
3106
3107   if (use_digest) { // digest login
3108     query.appendChild(iq.buildNode('digest', {xmlns: 'jabber:iq:auth'},
3109                                    hex_sha1(this.streamid + this.pass)));
3110   } else if (this.allow_plain) { // use plaintext auth
3111     query.appendChild(iq.buildNode('password', {xmlns: 'jabber:iq:auth'},
3112                                    this.pass));
3113   } else {
3114     this.oDbg.log("no valid login mechanism found",1);
3115     this.disconnect();
3116     return false;
3117   }
3118
3119   this.send(iq,this._doLegacyAuthDone);
3120 };
3121
3122 /**
3123  * @private
3124  */
3125 JSJaCConnection.prototype._doLegacyAuthDone = function(iq) {
3126   if (iq.getType() != 'result') { // auth' failed
3127     if (iq.getType() == 'error')
3128       this._handleEvent('onerror',iq.getChild('error'));
3129     this.disconnect();
3130   } else
3131     this._handleEvent('onconnect');
3132 };
3133
3134 /**
3135  * @private
3136  */
3137 JSJaCConnection.prototype._doSASLAuth = function() {
3138   if (this.authtype == 'nonsasl' || this.authtype == 'anonymous')
3139     return false;
3140
3141   if (this.authtype == 'saslanon') {
3142     if (this.mechs['ANONYMOUS']) {
3143       this.oDbg.log("SASL using mechanism 'ANONYMOUS'",2);
3144       return this._sendRaw("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>",
3145                            this._doSASLAuthDone);
3146     }
3147     this.oDbg.log("SASL ANONYMOUS requested but not supported",1);
3148   } else {
3149     if (this.mechs['DIGEST-MD5']) {
3150       this.oDbg.log("SASL using mechanism 'DIGEST-MD5'",2);
3151       return this._sendRaw("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>",
3152                            this._doSASLAuthDigestMd5S1);
3153     } else if (this.allow_plain && this.mechs['PLAIN']) {
3154       this.oDbg.log("SASL using mechanism 'PLAIN'",2);
3155       var authStr = this.username+'@'+
3156       this.domain+String.fromCharCode(0)+
3157       this.username+String.fromCharCode(0)+
3158       this.pass;
3159       this.oDbg.log("authenticating with '"+authStr+"'",2);
3160       authStr = b64encode(authStr);
3161       return this._sendRaw("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>"+authStr+"</auth>",
3162                            this._doSASLAuthDone);
3163     }
3164     this.oDbg.log("No SASL mechanism applied",1);
3165     this.authtype = 'nonsasl'; // fallback
3166   }
3167   return false;
3168 };
3169
3170 /**
3171  * @private
3172  */
3173 JSJaCConnection.prototype._doSASLAuthDigestMd5S1 = function(el) {
3174   if (el.nodeName != "challenge") {
3175     this.oDbg.log("challenge missing",1);
3176     this._handleEvent('onerror',JSJaCError('401','auth','not-authorized'));
3177     this.disconnect();
3178   } else {
3179     var challenge = b64decode(el.firstChild.nodeValue);
3180     this.oDbg.log("got challenge: "+challenge,2);
3181     this._nonce = challenge.substring(challenge.indexOf("nonce=")+7);
3182     this._nonce = this._nonce.substring(0,this._nonce.indexOf("\""));
3183     this.oDbg.log("nonce: "+this._nonce,2);
3184     if (this._nonce == '' || this._nonce.indexOf('\"') != -1) {
3185       this.oDbg.log("nonce not valid, aborting",1);
3186       this.disconnect();
3187       return;
3188     }
3189
3190     this._digest_uri = "xmpp/";
3191     //     if (typeof(this.host) != 'undefined' && this.host != '') {
3192     //       this._digest-uri += this.host;
3193     //       if (typeof(this.port) != 'undefined' && this.port)
3194     //         this._digest-uri += ":" + this.port;
3195     //       this._digest-uri += '/';
3196     //     }
3197     this._digest_uri += this.domain;
3198
3199     this._cnonce = cnonce(14);
3200
3201     this._nc = '00000001';
3202
3203     var A1 = str_md5(this.username+':'+this.domain+':'+this.pass)+
3204     ':'+this._nonce+':'+this._cnonce;
3205
3206     var A2 = 'AUTHENTICATE:'+this._digest_uri;
3207
3208     var response = hex_md5(hex_md5(A1)+':'+this._nonce+':'+this._nc+':'+
3209                            this._cnonce+':auth:'+hex_md5(A2));
3210
3211     var rPlain = 'username="'+this.username+'",realm="'+this.domain+
3212     '",nonce="'+this._nonce+'",cnonce="'+this._cnonce+'",nc="'+this._nc+
3213     '",qop=auth,digest-uri="'+this._digest_uri+'",response="'+response+
3214     '",charset="utf-8"';
3215
3216     this.oDbg.log("response: "+rPlain,2);
3217
3218     this._sendRaw("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"+
3219                   b64encode(rPlain)+"</response>",
3220                   this._doSASLAuthDigestMd5S2);
3221   }
3222 };
3223
3224 /**
3225  * @private
3226  */
3227 JSJaCConnection.prototype._doSASLAuthDigestMd5S2 = function(el) {
3228   if (el.nodeName == 'failure') {
3229     if (el.xml)
3230       this.oDbg.log("auth error: "+el.xml,1);
3231     else
3232       this.oDbg.log("auth error",1);
3233     this._handleEvent('onerror',JSJaCError('401','auth','not-authorized'));
3234     this.disconnect();
3235     return;
3236   }
3237
3238   var response = b64decode(el.firstChild.nodeValue);
3239   this.oDbg.log("response: "+response,2);
3240
3241   var rspauth = response.substring(response.indexOf("rspauth=")+8);
3242   this.oDbg.log("rspauth: "+rspauth,2);
3243
3244   var A1 = str_md5(this.username+':'+this.domain+':'+this.pass)+
3245   ':'+this._nonce+':'+this._cnonce;
3246
3247   var A2 = ':'+this._digest_uri;
3248
3249   var rsptest = hex_md5(hex_md5(A1)+':'+this._nonce+':'+this._nc+':'+
3250                         this._cnonce+':auth:'+hex_md5(A2));
3251   this.oDbg.log("rsptest: "+rsptest,2);
3252
3253   if (rsptest != rspauth) {
3254     this.oDbg.log("SASL Digest-MD5: server repsonse with wrong rspauth",1);
3255     this.disconnect();
3256     return;
3257   }
3258
3259     if (el.nodeName == 'success') {
3260         this._reInitStream(JSJaC.bind(this._doStreamBind, this));
3261     } else { // some extra turn
3262         this._sendRaw("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>",
3263                       this._doSASLAuthDone);
3264     }
3265 };
3266
3267 /**
3268  * @private
3269  */
3270 JSJaCConnection.prototype._doSASLAuthDone = function (el) {
3271     if (el.nodeName != 'success') {
3272         this.oDbg.log("auth failed",1);
3273         this._handleEvent('onerror',JSJaCError('401','auth','not-authorized'));
3274         this.disconnect();
3275     } else {
3276         this._reInitStream(JSJaC.bind(this._doStreamBind, this));
3277     }
3278 };
3279
3280 /**
3281  * @private
3282  */
3283 JSJaCConnection.prototype._doStreamBind = function() {
3284   var iq = new JSJaCIQ();
3285   iq.setIQ(null,'set','bind_1');
3286   iq.appendNode("bind", {xmlns: "urn:ietf:params:xml:ns:xmpp-bind"},
3287                 [["resource", this.resource]]);
3288   this.oDbg.log(iq.xml());
3289   this.send(iq,this._doXMPPSess);
3290 };
3291
3292 /**
3293  * @private
3294  */
3295 JSJaCConnection.prototype._doXMPPSess = function(iq) {
3296   if (iq.getType() != 'result' || iq.getType() == 'error') { // failed
3297     this.disconnect();
3298     if (iq.getType() == 'error')
3299       this._handleEvent('onerror',iq.getChild('error'));
3300     return;
3301   }
3302
3303   this.fulljid = iq.getChildVal("jid");
3304   this.jid = this.fulljid.substring(0,this.fulljid.lastIndexOf('/'));
3305
3306   iq = new JSJaCIQ();
3307   iq.setIQ(null,'set','sess_1');
3308   iq.appendNode("session", {xmlns: "urn:ietf:params:xml:ns:xmpp-session"},
3309                 []);
3310   this.oDbg.log(iq.xml());
3311   this.send(iq,this._doXMPPSessDone);
3312 };
3313
3314 /**
3315  * @private
3316  */
3317 JSJaCConnection.prototype._doXMPPSessDone = function(iq) {
3318   if (iq.getType() != 'result' || iq.getType() == 'error') { // failed
3319     this.disconnect();
3320     if (iq.getType() == 'error')
3321       this._handleEvent('onerror',iq.getChild('error'));
3322     return;
3323   } else
3324     this._handleEvent('onconnect');
3325 };
3326
3327 /**
3328  * @private
3329  */
3330 JSJaCConnection.prototype._handleEvent = function(event,arg) {
3331   event = event.toLowerCase(); // don't be case-sensitive here
3332   this.oDbg.log("incoming event '"+event+"'",3);
3333   if (!this._events[event])
3334     return;
3335   this.oDbg.log("handling event '"+event+"'",2);
3336   for (var i=0;i<this._events[event].length; i++) {
3337     var aEvent = this._events[event][i];
3338     if (typeof aEvent.handler == 'function') {
3339       try {
3340         if (arg) {
3341           if (arg.pType) { // it's a packet
3342             if ((!arg.getNode().hasChildNodes() && aEvent.childName != '*') ||
3343                                 (arg.getNode().hasChildNodes() &&
3344                                  !arg.getChild(aEvent.childName, aEvent.childNS)))
3345               continue;
3346             if (aEvent.type != '*' &&
3347                 arg.getType() != aEvent.type)
3348               continue;
3349             this.oDbg.log(aEvent.childName+"/"+aEvent.childNS+"/"+aEvent.type+" => match for handler "+aEvent.handler,3);
3350           }
3351           if (aEvent.handler(arg)) {
3352             // handled!
3353             break;
3354           }
3355         }
3356         else
3357           if (aEvent.handler()) {
3358             // handled!
3359             break;
3360           }
3361       } catch (e) {
3362
3363         if (e.fileName&&e.lineNumber) {
3364             this.oDbg.log(aEvent.handler+"\n>>>"+e.name+": "+ e.message+' in '+e.fileName+' line '+e.lineNumber,1);
3365         } else {
3366             this.oDbg.log(aEvent.handler+"\n>>>"+e.name+": "+ e.message,1);
3367         }
3368
3369       }
3370     }
3371   }
3372 };
3373
3374 /**
3375  * @private
3376  */
3377 JSJaCConnection.prototype._handlePID = function(aJSJaCPacket) {
3378   if (!aJSJaCPacket.getID())
3379     return false;
3380   for (var i in this._regIDs) {
3381     if (this._regIDs.hasOwnProperty(i) &&
3382         this._regIDs[i] && i == aJSJaCPacket.getID()) {
3383       var pID = aJSJaCPacket.getID();
3384       this.oDbg.log("handling "+pID,3);
3385       try {
3386         if (this._regIDs[i].cb.call(this, aJSJaCPacket, this._regIDs[i].arg) === false) {
3387           // don't unregister
3388           return false;
3389         } else {
3390           this._unregisterPID(pID);
3391           return true;
3392         }
3393       } catch (e) {
3394         // broken handler?
3395         this.oDbg.log(e.name+": "+ e.message, 1);
3396         this._unregisterPID(pID);
3397         return true;
3398       }
3399     }
3400   }
3401   return false;
3402 };
3403
3404 /**
3405  * @private
3406  */
3407 JSJaCConnection.prototype._handleResponse = function(req) {
3408   var rootEl = this._parseResponse(req);
3409
3410   if (!rootEl)
3411     return;
3412
3413   for (var i=0; i<rootEl.childNodes.length; i++) {
3414     if (this._sendRawCallbacks.length) {
3415       var cb = this._sendRawCallbacks[0];
3416       this._sendRawCallbacks = this._sendRawCallbacks.slice(1, this._sendRawCallbacks.length);
3417       cb.fn.call(this, rootEl.childNodes.item(i), cb.arg);
3418       continue;
3419     }
3420     this._inQ = this._inQ.concat(rootEl.childNodes.item(i));
3421   }
3422 };
3423
3424 /**
3425  * @private
3426  */
3427 JSJaCConnection.prototype._parseStreamFeatures = function(doc) {
3428     if (!doc) {
3429         this.oDbg.log("nothing to parse ... aborting",1);
3430         return false;
3431     }
3432
3433     var errorTag;
3434     if (doc.getElementsByTagNameNS) {
3435         errorTag = doc.getElementsByTagNameNS("http://etherx.jabber.org/streams", "error").item(0);
3436     } else {
3437         var errors = doc.getElementsByTagName("error");
3438         for (var i=0; i<errors.length; i++)
3439             if (errors.item(i).namespaceURI == "http://etherx.jabber.org/streams" ||
3440                 errors.item(i).getAttribute('xmlns') == "http://etherx.jabber.org/streams") {
3441                 errorTag = errors.item(i);
3442                 break;
3443             }
3444     }
3445
3446     if (errorTag) {
3447         this._setStatus("internal_server_error");
3448         clearTimeout(this._timeout); // remove timer
3449         clearInterval(this._interval);
3450         clearInterval(this._inQto);
3451         this._handleEvent('onerror',JSJaCError('503','cancel','session-terminate'));
3452         this._connected = false;
3453         this.oDbg.log("Disconnected.",1);
3454         this._handleEvent('ondisconnect');
3455         return false;
3456     }
3457     
3458     this.mechs = new Object();
3459     var lMec1 = doc.getElementsByTagName("mechanisms");
3460     this.has_sasl = false;
3461     for (var i=0; i<lMec1.length; i++)
3462         if (lMec1.item(i).getAttribute("xmlns") ==
3463             "urn:ietf:params:xml:ns:xmpp-sasl") {
3464             this.has_sasl=true;
3465             var lMec2 = lMec1.item(i).getElementsByTagName("mechanism");
3466             for (var j=0; j<lMec2.length; j++)
3467                 this.mechs[lMec2.item(j).firstChild.nodeValue] = true;
3468             break;
3469         }
3470     if (this.has_sasl)
3471         this.oDbg.log("SASL detected",2);
3472     else {
3473         this.oDbg.log("No support for SASL detected",2);
3474         return false;
3475     }
3476     
3477     // Get the server CAPS (if available)
3478     this.server_caps=null;
3479     var sCaps = doc.getElementsByTagName("c");
3480     for (var i=0; i<sCaps.length; i++) {
3481       var c_sCaps=sCaps.item(i);
3482       var x_sCaps=c_sCaps.getAttribute("xmlns");
3483       var v_sCaps=c_sCaps.getAttribute("ver");
3484       
3485       if ((x_sCaps == NS_CAPS) && v_sCaps) {
3486         this.server_caps=v_sCaps;
3487         break;
3488       }
3489     }
3490     
3491     return true;
3492 };
3493
3494 /**
3495  * @private
3496  */
3497 JSJaCConnection.prototype._process = function(timerval) {
3498   if (!this.connected()) {
3499     this.oDbg.log("Connection lost ...",1);
3500     if (this._interval)
3501       clearInterval(this._interval);
3502     return;
3503   }
3504
3505   this.setPollInterval(timerval);
3506
3507   if (this._timeout)
3508     clearTimeout(this._timeout);
3509
3510   var slot = this._getFreeSlot();
3511
3512   if (slot < 0)
3513     return;
3514
3515   if (typeof(this._req[slot]) != 'undefined' &&
3516       typeof(this._req[slot].r) != 'undefined' &&
3517       this._req[slot].r.readyState != 4) {
3518     this.oDbg.log("Slot "+slot+" is not ready");
3519     return;
3520   }
3521
3522   if (!this.isPolling() && this._pQueue.length == 0 &&
3523       this._req[(slot+1)%2] && this._req[(slot+1)%2].r.readyState != 4) {
3524     this.oDbg.log("all slots busy, standby ...", 2);
3525     return;
3526   }
3527
3528   if (!this.isPolling())
3529     this.oDbg.log("Found working slot at "+slot,2);
3530
3531   this._req[slot] = this._setupRequest(true);
3532
3533   /* setup onload handler for async send */
3534   this._req[slot].r.onreadystatechange =
3535   JSJaC.bind(function() {
3536                if (this._req[slot].r.readyState == 4) {
3537                  this._setStatus('processing');
3538                  this.oDbg.log("async recv: "+this._req[slot].r.responseText,4);
3539                  this._handleResponse(this._req[slot]);
3540
3541                  if (!this.connected())
3542                    return;
3543
3544                  // schedule next tick
3545                  if (this._pQueue.length) {
3546                    this._timeout = setTimeout(JSJaC.bind(this._process, this),100);
3547                  } else {
3548                    this.oDbg.log("scheduling next poll in "+this.getPollInterval()+
3549                                  " msec", 4);
3550                    this._timeout = setTimeout(JSJaC.bind(this._process, this),this.getPollInterval());
3551                  }
3552                }
3553              }, this);
3554
3555   try {
3556     this._req[slot].r.onerror =
3557       JSJaC.bind(function() {
3558                    if (!this.connected())
3559                      return;
3560                    this._errcnt++;
3561                    this.oDbg.log('XmlHttpRequest error ('+this._errcnt+')',1);
3562                    if (this._errcnt > JSJAC_ERR_COUNT) {
3563                      // abort
3564                      this._abort();
3565                      return false;
3566                    }
3567
3568                    this._setStatus('onerror_fallback');
3569
3570                    // schedule next tick
3571                    setTimeout(JSJaC.bind(this._resume, this),this.getPollInterval());
3572                    return false;
3573                  }, this);
3574   } catch(e) { } // well ... no onerror property available, maybe we
3575   // can catch the error somewhere else ...
3576
3577   var reqstr = this._getRequestString();
3578
3579   if (typeof(this._rid) != 'undefined') // remember request id if any
3580     this._req[slot].rid = this._rid;
3581
3582   this.oDbg.log("sending: " + reqstr,4);
3583   this._req[slot].r.send(reqstr);
3584 };
3585
3586 /**
3587  * @private
3588  */
3589 JSJaCConnection.prototype._registerPID = function(pID,cb,arg) {
3590   if (!pID || !cb)
3591     return false;
3592   this._regIDs[pID] = new Object();
3593   this._regIDs[pID].cb = cb;
3594   if (arg)
3595     this._regIDs[pID].arg = arg;
3596   this.oDbg.log("registered "+pID,3);
3597   return true;
3598 };
3599
3600 /**
3601  * partial function binding sendEmpty to callback
3602  * @private
3603  */
3604 JSJaCConnection.prototype._prepSendEmpty = function(cb, ctx) {
3605     return function() {
3606         ctx._sendEmpty(JSJaC.bind(cb, ctx));
3607     };
3608 };
3609
3610 /**
3611  * send empty request
3612  * waiting for stream id to be able to proceed with authentication
3613  * @private
3614  */
3615 JSJaCConnection.prototype._sendEmpty = function(cb) {
3616   var slot = this._getFreeSlot();
3617   this._req[slot] = this._setupRequest(true);
3618
3619   this._req[slot].r.onreadystatechange =
3620   JSJaC.bind(function() {
3621                if (this._req[slot].r.readyState == 4) {
3622                  this.oDbg.log("async recv: "+this._req[slot].r.responseText,4);
3623                    cb(this._req[slot].r); // handle response
3624                }
3625              },this);
3626
3627   if (typeof(this._req[slot].r.onerror) != 'undefined') {
3628     this._req[slot].r.onerror =
3629       JSJaC.bind(function(e) {
3630                    this.oDbg.log('XmlHttpRequest error',1);
3631                    return false;
3632                  }, this);
3633   }
3634
3635   var reqstr = this._getRequestString();
3636   this.oDbg.log("sending: " + reqstr,4);
3637   this._req[slot].r.send(reqstr);
3638 };
3639
3640 /**
3641  * @private
3642  */
3643 JSJaCConnection.prototype._sendRaw = function(xml,cb,arg) {
3644   if (cb)
3645     this._sendRawCallbacks.push({fn: cb, arg: arg});
3646
3647   this._pQueue.push(xml);
3648   this._process();
3649
3650   return true;
3651 };
3652
3653 /**
3654  * @private
3655  */
3656 JSJaCConnection.prototype._setStatus = function(status) {
3657   if (!status || status == '')
3658     return;
3659   if (status != this._status) { // status changed!
3660     this._status = status;
3661     this._handleEvent('onstatuschanged', status);
3662     this._handleEvent('status_changed', status);
3663   }
3664 };
3665
3666 /**
3667  * @private
3668  */
3669 JSJaCConnection.prototype._unregisterPID = function(pID) {
3670   if (!this._regIDs[pID])
3671     return false;
3672   this._regIDs[pID] = null;
3673   this.oDbg.log("unregistered "+pID,3);
3674   return true;
3675 };
3676
3677
3678 /**
3679  * @fileoverview All stuff related to HTTP Binding
3680  * @author Stefan Strigler steve@zeank.in-berlin.de
3681  * @version $Revision$
3682  */
3683
3684 /**
3685  * Instantiates an HTTP Binding session
3686  * @class Implementation of {@link
3687  * http://www.xmpp.org/extensions/xep-0206.html XMPP Over BOSH}
3688  * formerly known as HTTP Binding.
3689  * @extends JSJaCConnection
3690  * @constructor
3691  */
3692 function JSJaCHttpBindingConnection(oArg) {
3693   /**
3694    * @ignore
3695    */
3696   this.base = JSJaCConnection;
3697   this.base(oArg);
3698
3699   // member vars
3700   /**
3701    * @private
3702    */
3703   this._hold = JSJACHBC_MAX_HOLD;
3704   /**
3705    * @private
3706    */
3707   this._inactivity = 0;
3708   /**
3709    * @private
3710    */
3711   this._last_requests = new Object(); // 'hash' storing hold+1 last requests
3712   /**
3713    * @private
3714    */
3715   this._last_rid = 0;                 // I know what you did last summer
3716   /**
3717    * @private
3718    */
3719   this._min_polling = 0;
3720
3721   /**
3722    * @private
3723    */
3724   this._pause = 0;
3725   /**
3726    * @private
3727    */
3728   this._wait = JSJACHBC_MAX_WAIT;
3729 }
3730 JSJaCHttpBindingConnection.prototype = new JSJaCConnection();
3731
3732 /**
3733  * Inherit an instantiated HTTP Binding session
3734  */
3735 JSJaCHttpBindingConnection.prototype.inherit = function(oArg) {
3736   if (oArg.jid) {
3737     var oJid = new JSJaCJID(oArg.jid);
3738     this.domain = oJid.getDomain();
3739     this.username = oJid.getNode();
3740     this.resource = oJid.getResource();
3741   } else {
3742     this.domain = oArg.domain || 'localhost';
3743     this.username = oArg.username;
3744     this.resource = oArg.resource;
3745   }
3746   this._sid = oArg.sid;
3747   this._rid = oArg.rid;
3748   this._min_polling = oArg.polling;
3749   this._inactivity = oArg.inactivity;
3750   this._setHold(oArg.requests-1);
3751   this.setPollInterval(this._timerval);
3752   if (oArg.wait)
3753     this._wait = oArg.wait; // for whatever reason
3754
3755   this._connected = true;
3756
3757   this._handleEvent('onconnect');
3758
3759   this._interval= setInterval(JSJaC.bind(this._checkQueue, this),
3760                               JSJAC_CHECKQUEUEINTERVAL);
3761   this._inQto = setInterval(JSJaC.bind(this._checkInQ, this),
3762                             JSJAC_CHECKINQUEUEINTERVAL);
3763   this._timeout = setTimeout(JSJaC.bind(this._process, this),
3764                              this.getPollInterval());
3765 };
3766
3767 /**
3768  * Sets poll interval
3769  * @param {int} timerval the interval in seconds
3770  */
3771 JSJaCHttpBindingConnection.prototype.setPollInterval = function(timerval) {
3772   if (timerval && !isNaN(timerval)) {
3773     if (!this.isPolling())
3774       this._timerval = 100;
3775     else if (this._min_polling && timerval < this._min_polling*1000)
3776       this._timerval = this._min_polling*1000;
3777     else if (this._inactivity && timerval > this._inactivity*1000)
3778       this._timerval = this._inactivity*1000;
3779     else
3780       this._timerval = timerval;
3781   }
3782   return this._timerval;
3783 };
3784
3785 /**
3786  * whether this session is in polling mode
3787  * @type boolean
3788  */
3789 JSJaCHttpBindingConnection.prototype.isPolling = function() { return (this._hold == 0) };
3790
3791 /**
3792  * @private
3793  */
3794 JSJaCHttpBindingConnection.prototype._getFreeSlot = function() {
3795   for (var i=0; i<this._hold+1; i++)
3796     if (typeof(this._req[i]) == 'undefined' || typeof(this._req[i].r) == 'undefined' || this._req[i].r.readyState == 4)
3797       return i;
3798   return -1; // nothing found
3799 };
3800
3801 /**
3802  * @private
3803  */
3804 JSJaCHttpBindingConnection.prototype._getHold = function() { return this._hold; };
3805
3806 /**
3807  * @private
3808  */
3809 JSJaCHttpBindingConnection.prototype._getRequestString = function(raw, last) {
3810   raw = raw || '';
3811   var reqstr = '';
3812
3813   // check if we're repeating a request
3814
3815   if (this._rid <= this._last_rid && typeof(this._last_requests[this._rid]) != 'undefined') // repeat!
3816     reqstr = this._last_requests[this._rid].xml;
3817   else { // grab from queue
3818     var xml = '';
3819     while (this._pQueue.length) {
3820       var curNode = this._pQueue[0];
3821       xml += curNode;
3822       this._pQueue = this._pQueue.slice(1,this._pQueue.length);
3823     }
3824
3825     reqstr = "<body xml:lang='"+XML_LANG+"' rid='"+this._rid+"' sid='"+this._sid+"' xmlns='http://jabber.org/protocol/httpbind' ";
3826     if (JSJAC_HAVEKEYS) {
3827       reqstr += "key='"+this._keys.getKey()+"' ";
3828       if (this._keys.lastKey()) {
3829         this._keys = new JSJaCKeys(hex_sha1,this.oDbg);
3830         reqstr += "newkey='"+this._keys.getKey()+"' ";
3831       }
3832     }
3833     if (last)
3834       reqstr += "type='terminate'";
3835     else if (this._reinit) {
3836       if (JSJACHBC_USE_BOSH_VER)
3837         reqstr += "xmpp:restart='true' xmlns:xmpp='urn:xmpp:xbosh' to='"+this.domain+"'";
3838       this._reinit = false;
3839     }
3840
3841     if (xml != '' || raw != '') {
3842       reqstr += ">" + raw + xml + "</body>";
3843     } else {
3844       reqstr += "/>";
3845     }
3846
3847     this._last_requests[this._rid] = new Object();
3848     this._last_requests[this._rid].xml = reqstr;
3849     this._last_rid = this._rid;
3850
3851     for (var i in this._last_requests)
3852       if (this._last_requests.hasOwnProperty(i) &&
3853           i < this._rid-this._hold)
3854         delete(this._last_requests[i]); // truncate
3855   }
3856
3857   return reqstr;
3858 };
3859
3860 /**
3861  * @private
3862  */
3863 JSJaCHttpBindingConnection.prototype._getInitialRequestString = function() {
3864   var reqstr = "<body xml:lang='"+XML_LANG+"' content='text/xml; charset=utf-8' hold='"+this._hold+"' xmlns='http://jabber.org/protocol/httpbind' to='"+this.authhost+"' wait='"+this._wait+"' rid='"+this._rid+"'";
3865   if (this.secure)
3866     reqstr += " secure='"+this.secure+"'";
3867   if (JSJAC_HAVEKEYS) {
3868     this._keys = new JSJaCKeys(hex_sha1,this.oDbg); // generate first set of keys
3869     key = this._keys.getKey();
3870     reqstr += " newkey='"+key+"'";
3871   }
3872
3873   if (JSJACHBC_USE_BOSH_VER) {
3874     reqstr += " ver='" + JSJACHBC_BOSH_VERSION + "'";
3875     reqstr += " xmlns:xmpp='urn:xmpp:xbosh'";
3876     if (this.authtype == 'sasl' || this.authtype == 'saslanon')
3877       reqstr += " xmpp:version='1.0'";
3878   }
3879   reqstr += "/>";
3880   return reqstr;
3881 };
3882
3883 /**
3884  * @private
3885  */
3886 JSJaCHttpBindingConnection.prototype._getStreamID = function(req) {
3887
3888   this.oDbg.log(req.responseText,4);
3889
3890   if (!req.responseXML || !req.responseXML.documentElement) {
3891     this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
3892     return;
3893   }
3894   var body = req.responseXML.documentElement;
3895   
3896   // any session error?
3897   if(body.getAttribute('type') == 'terminate') {
3898     this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
3899     return;
3900   }
3901   
3902   // extract stream id used for non-SASL authentication
3903   if (body.getAttribute('authid')) {
3904     this.streamid = body.getAttribute('authid');
3905     this.oDbg.log("got streamid: "+this.streamid,2);
3906   }
3907
3908   if (!this._parseStreamFeatures(body)) {
3909       this._sendEmpty(JSJaC.bind(this._getStreamID, this));
3910       return;
3911   }
3912
3913   this._timeout = setTimeout(JSJaC.bind(this._process, this),
3914                              this.getPollInterval());
3915
3916   if (this.register)
3917     this._doInBandReg();
3918   else
3919     this._doAuth();
3920 };
3921
3922 /**
3923  * @private
3924  */
3925 JSJaCHttpBindingConnection.prototype._getSuspendVars = function() {
3926   return ('host,port,secure,_rid,_last_rid,_wait,_min_polling,_inactivity,_hold,_last_requests,_pause').split(',');
3927 };
3928
3929 /**
3930  * @private
3931  */
3932 JSJaCHttpBindingConnection.prototype._handleInitialResponse = function(req) {
3933   try {
3934     // This will throw an error on Mozilla when the connection was refused
3935     this.oDbg.log(req.getAllResponseHeaders(),4);
3936     this.oDbg.log(req.responseText,4);
3937   } catch(ex) {
3938     this.oDbg.log("No response",4);
3939   }
3940
3941   if (req.status != 200 || !req.responseXML) {
3942     this.oDbg.log("initial response broken (status: "+req.status+")",1);
3943     this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
3944     return;
3945   }
3946   var body = req.responseXML.documentElement;
3947
3948   if (!body || body.tagName != 'body' || body.namespaceURI != 'http://jabber.org/protocol/httpbind') {
3949     this.oDbg.log("no body element or incorrect body in initial response",1);
3950     this._handleEvent("onerror",JSJaCError("500","wait","internal-service-error"));
3951     return;
3952   }
3953
3954   // Check for errors from the server
3955   if (body.getAttribute("type") == "terminate") {
3956     this.oDbg.log("invalid response:\n" + req.responseText,1);
3957     clearTimeout(this._timeout); // remove timer
3958     this._connected = false;
3959     this.oDbg.log("Disconnected.",1);
3960     this._handleEvent('ondisconnect');
3961     this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
3962     return;
3963   }
3964
3965   // get session ID
3966   this._sid = body.getAttribute('sid');
3967   this.oDbg.log("got sid: "+this._sid,2);
3968
3969   // get attributes from response body
3970   if (body.getAttribute('polling'))
3971     this._min_polling = body.getAttribute('polling');
3972
3973   if (body.getAttribute('inactivity'))
3974     this._inactivity = body.getAttribute('inactivity');
3975
3976   if (body.getAttribute('requests'))
3977     this._setHold(body.getAttribute('requests')-1);
3978   this.oDbg.log("set hold to " + this._getHold(),2);
3979
3980   if (body.getAttribute('ver'))
3981     this._bosh_version = body.getAttribute('ver');
3982
3983   if (body.getAttribute('maxpause'))
3984     this._pause = Number.min(body.getAttribute('maxpause'), JSJACHBC_MAXPAUSE);
3985
3986   // must be done after response attributes have been collected
3987   this.setPollInterval(this._timerval);
3988
3989   /* start sending from queue for not polling connections */
3990   this._connected = true;
3991
3992   this._inQto = setInterval(JSJaC.bind(this._checkInQ, this),
3993                             JSJAC_CHECKINQUEUEINTERVAL);
3994   this._interval= setInterval(JSJaC.bind(this._checkQueue, this),
3995                               JSJAC_CHECKQUEUEINTERVAL);
3996
3997   /* wait for initial stream response to extract streamid needed
3998    * for digest auth
3999    */
4000   this._getStreamID(req);
4001 };
4002
4003 /**
4004  * @private
4005  */
4006 JSJaCHttpBindingConnection.prototype._parseResponse = function(req) {
4007   if (!this.connected() || !req)
4008     return null;
4009
4010   var r = req.r; // the XmlHttpRequest
4011
4012   try {
4013     if (r.status == 404 || r.status == 403) {
4014       // connection manager killed session
4015       this._abort();
4016       return null;
4017     }
4018
4019     if (r.status != 200 || !r.responseXML) {
4020       this._errcnt++;
4021       var errmsg = "invalid response ("+r.status+"):\n" + r.getAllResponseHeaders()+"\n"+r.responseText;
4022       if (!r.responseXML)
4023         errmsg += "\nResponse failed to parse!";
4024       this.oDbg.log(errmsg,1);
4025       if (this._errcnt > JSJAC_ERR_COUNT) {
4026         // abort
4027         this._abort();
4028         return null;
4029       }
4030
4031       if (this.connected()) {
4032         this.oDbg.log("repeating ("+this._errcnt+")",1);
4033         this._setStatus('proto_error_fallback');
4034
4035         // schedule next tick
4036         setTimeout(JSJaC.bind(this._resume, this),
4037                    this.getPollInterval());
4038       }
4039
4040       return null;
4041     }
4042   } catch (e) {
4043     this.oDbg.log("XMLHttpRequest error: status not available", 1);
4044           this._errcnt++;
4045           if (this._errcnt > JSJAC_ERR_COUNT) {
4046             // abort
4047             this._abort();
4048           } else {
4049       if (this.connected()) {
4050               this.oDbg.log("repeating ("+this._errcnt+")",1);
4051
4052               this._setStatus('proto_error_fallback');
4053
4054               // schedule next tick
4055               setTimeout(JSJaC.bind(this._resume, this),
4056                    this.getPollInterval());
4057       }
4058     }
4059     return null;
4060   }
4061
4062   var body = r.responseXML.documentElement;
4063   if (!body || body.tagName != 'body' ||
4064           body.namespaceURI != 'http://jabber.org/protocol/httpbind') {
4065     this.oDbg.log("invalid response:\n" + r.responseText,1);
4066
4067     clearTimeout(this._timeout); // remove timer
4068     clearInterval(this._interval);
4069     clearInterval(this._inQto);
4070
4071     this._connected = false;
4072     this.oDbg.log("Disconnected.",1);
4073     this._handleEvent('ondisconnect');
4074
4075     this._setStatus('internal_server_error');
4076     this._handleEvent('onerror',
4077                       JSJaCError('500','wait','internal-server-error'));
4078
4079     return null;
4080   }
4081
4082   if (typeof(req.rid) != 'undefined' && this._last_requests[req.rid]) {
4083     if (this._last_requests[req.rid].handled) {
4084       this.oDbg.log("already handled "+req.rid,2);
4085       return null;
4086     } else
4087       this._last_requests[req.rid].handled = true;
4088   }
4089
4090
4091   // Check for errors from the server
4092   if (body.getAttribute("type") == "terminate") {
4093     // read condition
4094     var condition = body.getAttribute('condition');
4095
4096     if (condition != "item-not-found") {
4097       this.oDbg.log("session terminated:\n" + r.responseText,1);
4098
4099       clearTimeout(this._timeout); // remove timer
4100       clearInterval(this._interval);
4101       clearInterval(this._inQto);
4102
4103       try {
4104         removeDB('jsjac', 'state');
4105       } catch (e) {}
4106
4107       this._connected = false;
4108
4109       if (condition == "remote-stream-error")
4110         if (body.getElementsByTagName("conflict").length > 0)
4111           this._setStatus("session-terminate-conflict");
4112       if (condition == null)
4113         condition = 'session-terminate';
4114       this._handleEvent('onerror',JSJaCError('503','cancel',condition));
4115
4116       this.oDbg.log("Aborting remaining connections",4);
4117
4118       for (var i=0; i<this._hold+1; i++) {
4119         try {
4120           this._req[i].r.abort();
4121         } catch(e) { this.oDbg.log(e, 1); }
4122       }
4123
4124       this.oDbg.log("parseResponse done with terminating", 3);
4125
4126       this.oDbg.log("Disconnected.",1);
4127       this._handleEvent('ondisconnect');
4128     } else {
4129       this._errcnt++;
4130       if (this._errcnt > JSJAC_ERR_COUNT)
4131         this._abort();
4132     }
4133     return null;
4134   }
4135
4136   // no error
4137   this._errcnt = 0;
4138   return r.responseXML.documentElement;
4139 };
4140
4141 /**
4142  * @private
4143  */
4144 JSJaCHttpBindingConnection.prototype._reInitStream = function(cb) {
4145     // tell http binding to reinit stream with/before next request
4146     this._reinit = true;
4147
4148     this._sendEmpty(this._prepReInitStreamWait(cb));
4149 };
4150
4151
4152 JSJaCHttpBindingConnection.prototype._prepReInitStreamWait = function(cb) {
4153     return JSJaC.bind(function(req) {
4154         this._reInitStreamWait(req, cb);
4155     }, this);
4156 };
4157
4158 /**
4159  * @private
4160  */
4161 JSJaCHttpBindingConnection.prototype._reInitStreamWait = function(req, cb) {
4162     this.oDbg.log("checking for stream features");
4163     var doc = req.responseXML.documentElement;
4164     this.oDbg.log(doc);
4165     if (doc.getElementsByTagNameNS) {
4166         this.oDbg.log("checking with namespace");
4167         var features = doc.getElementsByTagNameNS('http://etherx.jabber.org/streams',
4168                                                 'features').item(0);
4169         if (features) {
4170             var bind = features.getElementsByTagNameNS('urn:ietf:params:xml:ns:xmpp-bind',
4171                                                        'bind').item(0);
4172         }
4173     } else {
4174         var featuresNL = doc.getElementsByTagName('stream:features');
4175         for (var i=0, l=featuresNL.length; i<l; i++) {
4176             if (featuresNL.item(i).namespaceURI == 'http://etherx.jabber.org/streams' ||
4177                 featuresNL.item(i).getAttribute('xmlns') == 
4178                 'http://etherx.jabber.org/streams') {
4179                 var features = featuresNL.item(i);
4180                 break;
4181             }
4182         }
4183         if (features) {
4184             var bind = features.getElementsByTagName('bind');
4185             for (var i=0, l=bind.length; i<l; i++) {
4186                 if (bind.item(i).namespaceURI == 'urn:ietf:params:xml:ns:xmpp-bind' ||
4187                     bind.item(i).getAttribute('xmlns') == 
4188                     'urn:ietf:params:xml:ns:xmpp-bind') {
4189                     bind = bind.item(i);
4190                     break;
4191                 }
4192             }
4193         }
4194     }
4195     this.oDbg.log(features);
4196     this.oDbg.log(bind);
4197     
4198     if (features) {
4199         if (bind) {
4200             cb();
4201         } else {
4202             this.oDbg.log("no bind feature - giving up",1);
4203             this._handleEvent('onerror',JSJaCError('503','cancel',"service-unavailable"));
4204             this._connected = false;
4205             this.oDbg.log("Disconnected.",1);
4206             this._handleEvent('ondisconnect');
4207         }
4208     } else {
4209         // wait
4210         this._sendEmpty(this._prepReInitStreamWait(cb));
4211     }
4212 };
4213
4214 /**
4215  * @private
4216  */
4217 JSJaCHttpBindingConnection.prototype._resume = function() {
4218   /* make sure to repeat last request as we can be sure that
4219    * it had failed (only if we're not using the 'pause' attribute
4220    */
4221   if (this._pause == 0 && this._rid >= this._last_rid)
4222     this._rid = this._last_rid-1;
4223
4224   this._process();
4225 };
4226
4227 /**
4228  * @private
4229  */
4230 JSJaCHttpBindingConnection.prototype._setHold = function(hold)  {
4231   if (!hold || isNaN(hold) || hold < 0)
4232     hold = 0;
4233   else if (hold > JSJACHBC_MAX_HOLD)
4234     hold = JSJACHBC_MAX_HOLD;
4235   this._hold = hold;
4236   return this._hold;
4237 };
4238
4239 /**
4240  * @private
4241  */
4242 JSJaCHttpBindingConnection.prototype._setupRequest = function(async) {
4243   var req = new Object();
4244   var r = XmlHttp.create();
4245   try {
4246     r.open("POST",this._httpbase,async);
4247     r.setRequestHeader('Content-Type','text/xml; charset=utf-8');
4248   } catch(e) { this.oDbg.log(e,1); }
4249   req.r = r;
4250   this._rid++;
4251   req.rid = this._rid;
4252   return req;
4253 };
4254
4255 /**
4256  * @private
4257  */
4258 JSJaCHttpBindingConnection.prototype._suspend = function() {
4259   if (this._pause == 0)
4260     return; // got nothing to do
4261
4262   var slot = this._getFreeSlot();
4263   // Intentionally synchronous
4264   this._req[slot] = this._setupRequest(false);
4265
4266   var reqstr = "<body xml:lang='"+XML_LANG+"' pause='"+this._pause+"' xmlns='http://jabber.org/protocol/httpbind' sid='"+this._sid+"' rid='"+this._rid+"'";
4267   if (JSJAC_HAVEKEYS) {
4268     reqstr += " key='"+this._keys.getKey()+"'";
4269     if (this._keys.lastKey()) {
4270       this._keys = new JSJaCKeys(hex_sha1,this.oDbg);
4271       reqstr += " newkey='"+this._keys.getKey()+"'";
4272     }
4273
4274   }
4275   reqstr += ">";
4276
4277   while (this._pQueue.length) {
4278     var curNode = this._pQueue[0];
4279     reqstr += curNode;
4280     this._pQueue = this._pQueue.slice(1,this._pQueue.length);
4281   }
4282
4283   //reqstr += "<presence type='unavailable' xmlns='jabber:client'/>";
4284   reqstr += "</body>";
4285
4286   this.oDbg.log("Disconnecting: " + reqstr,4);
4287   this._req[slot].r.send(reqstr);
4288 };