2014-03-12 14:52:47 +01:00
/ *
Jappix - An open social platform
This is the JSJaC library for Jappix ( from trunk )
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
Licenses : Mozilla Public License version 1.1 , GNU GPL , AGPL
Authors : Stefan Strigler , Valérian Saliou , Zash , Maranda
* /
/ * *
* @ fileoverview Magic dependency loading . Taken from script . aculo . us
* and modified to break it .
2014-11-25 23:42:38 +01:00
* @ author Stefan Strigler steve @ zeank . in - berlin . de
2014-11-25 20:12:58 +01:00
* @ version 1.3
2014-03-12 14:52:47 +01:00
* /
var JSJaC = {
2014-11-25 20:12:58 +01:00
Version : '1.3' ,
2014-03-12 14:52:47 +01:00
bind : function ( fn , obj , optArg ) {
return function ( arg ) {
return fn . apply ( obj , [ arg , optArg ] ) ;
} ;
}
} ;
/ * C o p y r i g h t 2 0 0 6 E r i k A r v i d s s o n
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ; you
* may not use this file except in compliance with the License . You
* may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or
* implied . See the License for the specific language governing
* permissions and limitations under the License .
* /
/ * *
* @ fileoverview Wrapper to make working with XmlHttpRequest and the
* DOM more convenient ( cross browser compliance ) .
* this code is taken from
* http : //webfx.eae.net/dhtml/xmlextras/xmlextras.html
* @ author Stefan Strigler steve @ zeank . in - berlin . de
2014-11-25 20:12:58 +01:00
* @ version 1.3
2014-03-12 14:52:47 +01:00
* /
/ * *
* XmlHttp factory
* @ private
* /
function XmlHttp ( ) { }
/ * *
* creates a cross browser compliant XmlHttpRequest object
* /
XmlHttp . create = function ( ) {
try {
// Are we cross-domain?
if ( ! BOSH _SAME _ORIGIN ) {
// Able to use CORS?
if ( window . XMLHttpRequest ) {
var req = new XMLHttpRequest ( ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
if ( req . withCredentials !== undefined )
return req ;
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
// Fallback on JSONP
2014-04-08 20:14:28 +02:00
return new jXHR ( ) ;
2014-03-12 14:52:47 +01:00
}
// Might be local-domain?
if ( window . XMLHttpRequest ) {
var req = new XMLHttpRequest ( ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
// some versions of Moz do not support the readyState property
// and the onreadystate event so we patch it!
if ( req . readyState == null ) {
2014-04-08 20:14:28 +02:00
req . readyState = 1 ;
req . addEventListener ( "load" , function ( ) {
req . readyState = 4 ;
if ( typeof req . onreadystatechange == "function" )
req . onreadystatechange ( ) ;
} , false ) ;
2014-03-12 14:52:47 +01:00
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
return req ;
}
if ( window . ActiveXObject ) {
return new ActiveXObject ( XmlHttp . getPrefix ( ) + ".XmlHttp" ) ;
}
}
catch ( ex ) { }
// fell through
throw new Error ( "Your browser does not support XmlHttp objects" ) ;
} ;
/ * *
* used to find the Automation server name
* @ private
* /
XmlHttp . getPrefix = function ( ) {
if ( XmlHttp . prefix ) // I know what you did last summer
return XmlHttp . prefix ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
var prefixes = [ "MSXML2" , "Microsoft" , "MSXML" , "MSXML3" ] ;
var o ;
for ( var i = 0 ; i < prefixes . length ; i ++ ) {
try {
// try to create the objects
o = new ActiveXObject ( prefixes [ i ] + ".XmlHttp" ) ;
return XmlHttp . prefix = prefixes [ i ] ;
}
catch ( ex ) { } ;
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
throw new Error ( "Could not find an installed XML parser" ) ;
} ;
/ * *
* XmlDocument factory
* @ private
* /
function XmlDocument ( ) { }
XmlDocument . create = function ( name , ns ) {
name = name || 'foo' ;
ns = ns || '' ;
try {
var doc ;
// DOM2
if ( document . implementation && document . implementation . createDocument ) {
doc = document . implementation . createDocument ( ns , name , null ) ;
// some versions of Moz do not support the readyState property
// and the onreadystate event so we patch it!
if ( doc . readyState == null ) {
2014-04-08 20:14:28 +02:00
doc . readyState = 1 ;
doc . addEventListener ( "load" , function ( ) {
doc . readyState = 4 ;
if ( typeof doc . onreadystatechange == "function" )
doc . onreadystatechange ( ) ;
} , false ) ;
2014-03-12 14:52:47 +01:00
}
} else if ( window . ActiveXObject ) {
doc = new ActiveXObject ( XmlDocument . getPrefix ( ) + ".DomDocument" ) ;
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
if ( ! doc . documentElement || doc . documentElement . tagName != name ||
( doc . documentElement . namespaceURI &&
doc . documentElement . namespaceURI != ns ) ) {
try {
if ( ns != '' )
doc . appendChild ( doc . createElement ( name ) ) .
setAttribute ( 'xmlns' , ns ) ;
else
doc . appendChild ( doc . createElement ( name ) ) ;
} catch ( dex ) {
doc = document . implementation . createDocument ( ns , name , null ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
if ( doc . documentElement == null )
doc . appendChild ( doc . createElement ( name ) ) ;
// fix buggy opera 8.5x
if ( ns != '' &&
doc . documentElement . getAttribute ( 'xmlns' ) != ns ) {
doc . documentElement . setAttribute ( 'xmlns' , ns ) ;
}
}
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
return doc ;
}
catch ( ex ) { }
throw new Error ( "Your browser does not support XmlDocument objects" ) ;
} ;
/ * *
* used to find the Automation server name
* @ private
* /
XmlDocument . getPrefix = function ( ) {
if ( XmlDocument . prefix )
return XmlDocument . prefix ;
var prefixes = [ "MSXML2" , "Microsoft" , "MSXML" , "MSXML3" ] ;
var o ;
for ( var i = 0 ; i < prefixes . length ; i ++ ) {
try {
// try to create the objects
o = new ActiveXObject ( prefixes [ i ] + ".DomDocument" ) ;
return XmlDocument . prefix = prefixes [ i ] ;
}
catch ( ex ) { } ;
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
throw new Error ( "Could not find an installed XML parser" ) ;
} ;
// Create the loadXML method
if ( typeof ( Document ) != 'undefined' && window . DOMParser ) {
/ * *
* XMLDocument did not extend the Document interface in some
* versions of Mozilla .
* @ private
* /
Document . prototype . loadXML = function ( s ) {
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
// parse the string to a new doc
var doc2 = ( new DOMParser ( ) ) . parseFromString ( s , "text/xml" ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
// remove all initial children
while ( this . hasChildNodes ( ) )
this . removeChild ( this . lastChild ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
// insert and import nodes
for ( var i = 0 ; i < doc2 . childNodes . length ; i ++ ) {
this . appendChild ( this . importNode ( doc2 . childNodes [ i ] , true ) ) ;
}
} ;
}
// Create xml getter for Mozilla
if ( window . XMLSerializer &&
window . Node && Node . prototype && Node . prototype . _ _defineGetter _ _ ) {
/ * *
* xml getter
*
* This serializes the DOM tree to an XML String
*
* Usage : var sXml = oNode . xml
* @ deprecated
* @ private
* /
// XMLDocument did not extend the Document interface in some versions
// of Mozilla. Extend both!
XMLDocument . prototype . _ _defineGetter _ _ ( "xml" , function ( ) {
return ( new XMLSerializer ( ) ) . serializeToString ( this ) ;
} ) ;
/ * *
* xml getter
*
* This serializes the DOM tree to an XML String
*
* Usage : var sXml = oNode . xml
* @ deprecated
* @ private
* /
Document . prototype . _ _defineGetter _ _ ( "xml" , function ( ) {
return ( new XMLSerializer ( ) ) . serializeToString ( this ) ;
} ) ;
/ * *
* xml getter
*
* This serializes the DOM tree to an XML String
*
* Usage : var sXml = oNode . xml
* @ deprecated
* @ private
* /
Node . prototype . _ _defineGetter _ _ ( "xml" , function ( ) {
return ( new XMLSerializer ( ) ) . serializeToString ( this ) ;
} ) ;
}
/ * *
* @ fileoverview Collection of functions to make live easier
* @ author Stefan Strigler
2014-11-25 20:12:58 +01:00
* @ version 1.3
2014-03-12 14:52:47 +01:00
* /
/ * *
* Convert special chars to HTML entities
* @ addon
* @ return The string with chars encoded for HTML
* @ type String
* /
String . prototype . htmlEnc = function ( ) {
if ( ! this )
return this ;
return this . replace ( /&(?!(amp|apos|gt|lt|quot);)/g , "&" )
. replace ( /</g , "<" )
. replace ( />/g , ">" )
. replace ( /\'/g , "'" )
. replace ( /\"/g , """ )
. replace ( /\n/g , "<br />" ) ;
} ;
/ * *
* Convert HTML entities to special chars
* @ addon
* @ return The normal string
* @ type String
* /
String . prototype . revertHtmlEnc = function ( ) {
if ( ! this )
return this ;
2014-04-08 20:14:28 +02:00
2014-03-12 14:52:47 +01:00
var str = this . replace ( /&/gi , '&' ) ;
str = str . replace ( /</gi , '<' ) ;
str = str . replace ( />/gi , '>' ) ;
str = str . replace ( /'/gi , '\'' ) ;
str = str . replace ( /"/gi , '\"' ) ;
str = str . replace ( /<br( )?(\/)?>/gi , '\n' ) ;
return str ;
} ;
/ * *
* Converts from jabber timestamps to JavaScript Date objects
* @ addon
* @ param { String } ts A string representing a jabber datetime timestamp as
* defined by { @ link http : //www.xmpp.org/extensions/xep-0082.html XEP-0082}
* @ return A javascript Date object corresponding to the jabber DateTime given
* @ type Date
* /
Date . jab2date = function ( ts ) {
// Timestamp
if ( ! isNaN ( ts ) )
2014-04-08 20:14:28 +02:00
return new Date ( ts * 1000 ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
// Get the UTC date
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 ) ) ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
if ( ts . substr ( ts . length - 6 , 1 ) != 'Z' ) { // there's an offset
var date _offset = date . getTimezoneOffset ( ) * 60 * 1000 ;
var offset = new Date ( ) ;
offset . setTime ( 0 ) ;
offset . setUTCHours ( ts . substr ( ts . length - 5 , 2 ) ) ;
offset . setUTCMinutes ( ts . substr ( ts . length - 2 , 2 ) ) ;
if ( ts . substr ( ts . length - 6 , 1 ) == '+' )
date . setTime ( date . getTime ( ) + offset . getTime ( ) + date _offset ) ;
else if ( ts . substr ( ts . length - 6 , 1 ) == '-' )
date . setTime ( date . getTime ( ) - offset . getTime ( ) + date _offset ) ;
}
return date ;
} ;
/ * *
* Takes a timestamp in the form of 2004 - 08 - 13 T12 : 07 : 04 + 02 : 00 as argument
* and converts it to some sort of humane readable format
* @ addon
* /
Date . hrTime = function ( ts ) {
return Date . jab2date ( ts ) . toLocaleString ( ) ;
} ;
2014-04-08 20:14:28 +02:00
/ * *
* Current timestamp .
* @ return Seconds since 1.1 . 1970.
* @ type int
* /
if ( ! Date . now ) {
Date . now = function ( ) { return new Date ( ) . getTime ( ) ; }
}
2014-11-25 23:42:38 +01:00
2014-04-08 20:14:28 +02:00
/ * *
2014-03-12 14:52:47 +01:00
* somewhat opposit to { @ link # hrTime }
* expects a javascript Date object as parameter and returns a jabber
* date string conforming to
* { @ link http : //www.xmpp.org/extensions/xep-0082.html XEP-0082}
* @ see # hrTime
* @ return The corresponding jabber DateTime string
* @ type String
* /
Date . prototype . jabberDate = function ( ) {
var padZero = function ( i ) {
if ( i < 10 ) return "0" + i ;
return i ;
} ;
var jDate = this . getUTCFullYear ( ) + "-" ;
jDate += padZero ( this . getUTCMonth ( ) + 1 ) + "-" ;
jDate += padZero ( this . getUTCDate ( ) ) + "T" ;
jDate += padZero ( this . getUTCHours ( ) ) + ":" ;
jDate += padZero ( this . getUTCMinutes ( ) ) + ":" ;
jDate += padZero ( this . getUTCSeconds ( ) ) + "Z" ;
return jDate ;
} ;
/ * *
* Determines the maximum of two given numbers
* @ addon
* @ param { Number } A a number
* @ param { Number } B another number
* @ return the maximum of A and B
* @ type Number
* /
Number . max = function ( A , B ) {
return ( A > B ) ? A : B ;
} ;
Number . min = function ( A , B ) {
return ( A < B ) ? A : B ;
} ;
/ * C o p y r i g h t ( c ) 1 9 9 8 - 2 0 0 7 , P a u l J o h n s t o n & C o n t r i b u t o r s
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions
* are met :
*
* Redistributions of source code must retain the above copyright
* notice , this list of conditions and the following
* disclaimer . Redistributions in binary form must reproduce the above
* copyright notice , this list of conditions and the following
* disclaimer in the documentation and / or other materials provided
* with the distribution .
*
* Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission .
*
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT
* LIMITED TO , THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT ,
* INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
* ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION )
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT ,
* STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE )
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE .
*
* /
/ * *
* @ fileoverview Collection of MD5 and SHA1 hashing and encoding
* methods .
* @ author Stefan Strigler steve @ zeank . in - berlin . de
* /
/ *
* A JavaScript implementation of the Secure Hash Algorithm , SHA - 1 , as defined
* in FIPS PUB 180 - 1
* Version 2.1 a Copyright Paul Johnston 2000 - 2002.
* Other contributors : Greg Holt , Andrew Kepert , Ydnar , Lostinet
* Distributed under the BSD License
* See http : //pajhome.org.uk/crypt/md5 for details.
* /
/ *
* Configurable variables . You may need to tweak these to be compatible with
* the server - side , but the defaults work in most cases .
* /
var hexcase = 0 ; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = "=" ; /* base-64 pad character. "=" for strict RFC compliance */
var chrsz = 8 ; /* bits per input character. 8 - ASCII; 16 - Unicode */
/ *
* These are the functions you ' ll usually want to call
* They take string arguments and return either hex or base - 64 encoded strings
* /
function hex _sha1 ( s ) { return binb2hex ( core _sha1 ( str2binb ( s ) , s . length * chrsz ) ) ; }
function b64 _sha1 ( s ) { return binb2b64 ( core _sha1 ( str2binb ( s ) , s . length * chrsz ) ) ; }
function str _sha1 ( s ) { return binb2str ( core _sha1 ( str2binb ( s ) , s . length * chrsz ) ) ; }
function hex _hmac _sha1 ( key , data ) { return binb2hex ( core _hmac _sha1 ( key , data ) ) ; }
function b64 _hmac _sha1 ( key , data ) { return binb2b64 ( core _hmac _sha1 ( key , data ) ) ; }
function str _hmac _sha1 ( key , data ) { return binb2str ( core _hmac _sha1 ( key , data ) ) ; }
/ *
* Perform a simple self - test to see if the VM is working
* /
function sha1 _vm _test ( )
{
return hex _sha1 ( "abc" ) == "a9993e364706816aba3e25717850c26c9cd0d89d" ;
}
/ *
* Calculate the SHA - 1 of an array of big - endian words , and a bit length
* /
function core _sha1 ( x , len )
{
/* append padding */
x [ len >> 5 ] |= 0x80 << ( 24 - len % 32 ) ;
x [ ( ( len + 64 >> 9 ) << 4 ) + 15 ] = len ;
var w = new Array ( 80 ) ;
var a = 1732584193 ;
var b = - 271733879 ;
var c = - 1732584194 ;
var d = 271733878 ;
var e = - 1009589776 ;
var i , j , t , olda , oldb , oldc , oldd , olde ;
for ( i = 0 ; i < x . length ; i += 16 )
{
olda = a ;
oldb = b ;
oldc = c ;
oldd = d ;
olde = e ;
for ( j = 0 ; j < 80 ; j ++ )
{
if ( j < 16 ) { w [ j ] = x [ i + j ] ; }
else { w [ j ] = rol ( w [ j - 3 ] ^ w [ j - 8 ] ^ w [ j - 14 ] ^ w [ j - 16 ] , 1 ) ; }
t = safe _add ( safe _add ( rol ( a , 5 ) , sha1 _ft ( j , b , c , d ) ) ,
safe _add ( safe _add ( e , w [ j ] ) , sha1 _kt ( j ) ) ) ;
e = d ;
d = c ;
c = rol ( b , 30 ) ;
b = a ;
a = t ;
}
a = safe _add ( a , olda ) ;
b = safe _add ( b , oldb ) ;
c = safe _add ( c , oldc ) ;
d = safe _add ( d , oldd ) ;
e = safe _add ( e , olde ) ;
}
return [ a , b , c , d , e ] ;
}
/ *
* Perform the appropriate triplet combination function for the current
* iteration
* /
function sha1 _ft ( t , b , c , d )
{
if ( t < 20 ) { return ( b & c ) | ( ( ~ b ) & d ) ; }
if ( t < 40 ) { return b ^ c ^ d ; }
if ( t < 60 ) { return ( b & c ) | ( b & d ) | ( c & d ) ; }
return b ^ c ^ d ;
}
/ *
* Determine the appropriate additive constant for the current iteration
* /
function sha1 _kt ( t )
{
return ( t < 20 ) ? 1518500249 : ( t < 40 ) ? 1859775393 :
( t < 60 ) ? - 1894007588 : - 899497514 ;
}
/ *
* Calculate the HMAC - SHA1 of a key and some data
* /
function core _hmac _sha1 ( key , data )
{
var bkey = str2binb ( key ) ;
if ( bkey . length > 16 ) { bkey = core _sha1 ( bkey , key . length * chrsz ) ; }
var ipad = new Array ( 16 ) , opad = new Array ( 16 ) ;
for ( var i = 0 ; i < 16 ; i ++ )
{
ipad [ i ] = bkey [ i ] ^ 0x36363636 ;
opad [ i ] = bkey [ i ] ^ 0x5C5C5C5C ;
}
var hash = core _sha1 ( ipad . concat ( str2binb ( data ) ) , 512 + data . length * chrsz ) ;
return core _sha1 ( opad . concat ( hash ) , 512 + 160 ) ;
}
/ *
* Add integers , wrapping at 2 ^ 32. This uses 16 - bit operations internally
* to work around bugs in some JS interpreters .
* /
function safe _add ( x , y )
{
var lsw = ( x & 0xFFFF ) + ( y & 0xFFFF ) ;
var msw = ( x >> 16 ) + ( y >> 16 ) + ( lsw >> 16 ) ;
return ( msw << 16 ) | ( lsw & 0xFFFF ) ;
}
/ *
* Bitwise rotate a 32 - bit number to the left .
* /
function rol ( num , cnt )
{
return ( num << cnt ) | ( num >>> ( 32 - cnt ) ) ;
}
/ *
* Convert an 8 - bit or 16 - bit string to an array of big - endian words
* In 8 - bit function , characters > 255 have their hi - byte silently ignored .
* /
function str2binb ( str )
{
var bin = [ ] ;
var mask = ( 1 << chrsz ) - 1 ;
for ( var i = 0 ; i < str . length * chrsz ; i += chrsz )
{
bin [ i >> 5 ] |= ( str . charCodeAt ( i / chrsz ) & mask ) << ( 32 - chrsz - i % 32 ) ;
}
return bin ;
}
/ *
* Convert an array of big - endian words to a string
* /
function binb2str ( bin )
{
var str = "" ;
var mask = ( 1 << chrsz ) - 1 ;
for ( var i = 0 ; i < bin . length * 32 ; i += chrsz )
{
str += String . fromCharCode ( ( bin [ i >> 5 ] >>> ( 32 - chrsz - i % 32 ) ) & mask ) ;
}
return str ;
}
/ *
* Convert an array of big - endian words to a hex string .
* /
function binb2hex ( binarray )
{
var hex _tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef" ;
var str = "" ;
for ( var i = 0 ; i < binarray . length * 4 ; i ++ )
{
str += hex _tab . charAt ( ( binarray [ i >> 2 ] >> ( ( 3 - i % 4 ) * 8 + 4 ) ) & 0xF ) +
hex _tab . charAt ( ( binarray [ i >> 2 ] >> ( ( 3 - i % 4 ) * 8 ) ) & 0xF ) ;
}
return str ;
}
/ *
* Convert an array of big - endian words to a base - 64 string
* /
function binb2b64 ( binarray )
{
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ;
var str = "" ;
var triplet , j ;
for ( var i = 0 ; i < binarray . length * 4 ; i += 3 )
{
triplet = ( ( ( binarray [ i >> 2 ] >> 8 * ( 3 - i % 4 ) ) & 0xFF ) << 16 ) |
( ( ( binarray [ i + 1 >> 2 ] >> 8 * ( 3 - ( i + 1 ) % 4 ) ) & 0xFF ) << 8 ) |
( ( binarray [ i + 2 >> 2 ] >> 8 * ( 3 - ( i + 2 ) % 4 ) ) & 0xFF ) ;
for ( j = 0 ; j < 4 ; j ++ )
{
if ( i * 8 + j * 6 > binarray . length * 32 ) { str += b64pad ; }
else { str += tab . charAt ( ( triplet >> 6 * ( 3 - j ) ) & 0x3F ) ; }
}
}
return str ;
}
/ *
* A JavaScript implementation of the RSA Data Security , Inc . MD5 Message
* Digest Algorithm , as defined in RFC 1321.
* Version 2.2 Copyright ( C ) Paul Johnston 1999 - 2009
* Other contributors : Greg Holt , Andrew Kepert , Ydnar , Lostinet
* Distributed under the BSD License
* See http : //pajhome.org.uk/crypt/md5 for more info.
* /
/ *
* These are the functions you ' ll usually want to call
* They take string arguments and return either hex or base - 64 encoded strings
* /
function hex _md5 ( s ) { return rstr2hex ( rstr _md5 ( str2rstr _utf8 ( s ) ) ) ; }
function b64 _md5 ( s ) { return rstr2b64 ( rstr _md5 ( str2rstr _utf8 ( s ) ) ) ; }
function any _md5 ( s , e ) { return rstr2any ( rstr _md5 ( str2rstr _utf8 ( s ) ) , e ) ; }
function hex _hmac _md5 ( k , d )
{ return rstr2hex ( rstr _hmac _md5 ( str2rstr _utf8 ( k ) , str2rstr _utf8 ( d ) ) ) ; }
function b64 _hmac _md5 ( k , d )
{ return rstr2b64 ( rstr _hmac _md5 ( str2rstr _utf8 ( k ) , str2rstr _utf8 ( d ) ) ) ; }
function any _hmac _md5 ( k , d , e )
{ return rstr2any ( rstr _hmac _md5 ( str2rstr _utf8 ( k ) , str2rstr _utf8 ( d ) ) , e ) ; }
/ *
* Perform a simple self - test to see if the VM is working
* /
function md5 _vm _test ( )
{
return hex _md5 ( "abc" ) . toLowerCase ( ) == "900150983cd24fb0d6963f7d28e17f72" ;
}
/ *
* Calculate the MD5 of a raw string
* /
function rstr _md5 ( s )
{
return binl2rstr ( binl _md5 ( rstr2binl ( s ) , s . length * 8 ) ) ;
}
/ *
* Calculate the HMAC - MD5 , of a key and some data ( raw strings )
* /
function rstr _hmac _md5 ( key , data )
{
var bkey = rstr2binl ( key ) ;
if ( bkey . length > 16 ) bkey = binl _md5 ( bkey , key . length * 8 ) ;
var ipad = Array ( 16 ) , opad = Array ( 16 ) ;
for ( var i = 0 ; i < 16 ; i ++ )
{
ipad [ i ] = bkey [ i ] ^ 0x36363636 ;
opad [ i ] = bkey [ i ] ^ 0x5C5C5C5C ;
}
var hash = binl _md5 ( ipad . concat ( rstr2binl ( data ) ) , 512 + data . length * 8 ) ;
return binl2rstr ( binl _md5 ( opad . concat ( hash ) , 512 + 128 ) ) ;
}
/ *
* Convert a raw string to a hex string
* /
function rstr2hex ( input )
{
try { hexcase } catch ( e ) { hexcase = 0 ; }
var hex _tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef" ;
var output = "" ;
var x ;
for ( var i = 0 ; i < input . length ; i ++ )
{
x = input . charCodeAt ( i ) ;
output += hex _tab . charAt ( ( x >>> 4 ) & 0x0F )
+ hex _tab . charAt ( x & 0x0F ) ;
}
return output ;
}
/ *
* Convert a raw string to a base - 64 string
* /
function rstr2b64 ( input )
{
try { b64pad } catch ( e ) { b64pad = '' ; }
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ;
var output = "" ;
var len = input . length ;
for ( var i = 0 ; i < len ; i += 3 )
{
var triplet = ( input . charCodeAt ( i ) << 16 )
| ( i + 1 < len ? input . charCodeAt ( i + 1 ) << 8 : 0 )
| ( i + 2 < len ? input . charCodeAt ( i + 2 ) : 0 ) ;
for ( var j = 0 ; j < 4 ; j ++ )
{
if ( i * 8 + j * 6 > input . length * 8 ) output += b64pad ;
else output += tab . charAt ( ( triplet >>> 6 * ( 3 - j ) ) & 0x3F ) ;
}
}
return output ;
}
/ *
* Convert a raw string to an arbitrary string encoding
* /
function rstr2any ( input , encoding )
{
var divisor = encoding . length ;
var i , j , q , x , quotient ;
/* Convert to an array of 16-bit big-endian values, forming the dividend */
var dividend = Array ( Math . ceil ( input . length / 2 ) ) ;
for ( i = 0 ; i < dividend . length ; i ++ )
{
dividend [ i ] = ( input . charCodeAt ( i * 2 ) << 8 ) | input . charCodeAt ( i * 2 + 1 ) ;
}
/ *
* Repeatedly perform a long division . The binary array forms the dividend ,
* the length of the encoding is the divisor . Once computed , the quotient
* forms the dividend for the next step . All remainders are stored for later
* use .
* /
var full _length = Math . ceil ( input . length * 8 /
( Math . log ( encoding . length ) / Math . log ( 2 ) ) ) ;
var remainders = Array ( full _length ) ;
for ( j = 0 ; j < full _length ; j ++ )
{
quotient = Array ( ) ;
x = 0 ;
for ( i = 0 ; i < dividend . length ; i ++ )
{
x = ( x << 16 ) + dividend [ i ] ;
q = Math . floor ( x / divisor ) ;
x -= q * divisor ;
if ( quotient . length > 0 || q > 0 )
quotient [ quotient . length ] = q ;
}
remainders [ j ] = x ;
dividend = quotient ;
}
/* Convert the remainders to the output string */
var output = "" ;
for ( i = remainders . length - 1 ; i >= 0 ; i -- )
output += encoding . charAt ( remainders [ i ] ) ;
return output ;
}
/ *
* Encode a string as utf - 8.
* For efficiency , this assumes the input is valid utf - 16.
* /
function str2rstr _utf8 ( input )
{
var output = "" ;
var i = - 1 ;
var x , y ;
while ( ++ i < input . length )
{
/* Decode utf-16 surrogate pairs */
x = input . charCodeAt ( i ) ;
y = i + 1 < input . length ? input . charCodeAt ( i + 1 ) : 0 ;
if ( 0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF )
{
x = 0x10000 + ( ( x & 0x03FF ) << 10 ) + ( y & 0x03FF ) ;
i ++ ;
}
/* Encode output as utf-8 */
if ( x <= 0x7F )
output += String . fromCharCode ( x ) ;
else if ( x <= 0x7FF )
output += String . fromCharCode ( 0xC0 | ( ( x >>> 6 ) & 0x1F ) ,
0x80 | ( x & 0x3F ) ) ;
else if ( x <= 0xFFFF )
output += String . fromCharCode ( 0xE0 | ( ( x >>> 12 ) & 0x0F ) ,
0x80 | ( ( x >>> 6 ) & 0x3F ) ,
0x80 | ( x & 0x3F ) ) ;
else if ( x <= 0x1FFFFF )
output += String . fromCharCode ( 0xF0 | ( ( x >>> 18 ) & 0x07 ) ,
0x80 | ( ( x >>> 12 ) & 0x3F ) ,
0x80 | ( ( x >>> 6 ) & 0x3F ) ,
0x80 | ( x & 0x3F ) ) ;
}
return output ;
}
/ *
* Encode a string as utf - 16
* /
function str2rstr _utf16le ( input )
{
var output = "" ;
for ( var i = 0 ; i < input . length ; i ++ )
output += String . fromCharCode ( input . charCodeAt ( i ) & 0xFF ,
( input . charCodeAt ( i ) >>> 8 ) & 0xFF ) ;
return output ;
}
function str2rstr _utf16be ( input )
{
var output = "" ;
for ( var i = 0 ; i < input . length ; i ++ )
output += String . fromCharCode ( ( input . charCodeAt ( i ) >>> 8 ) & 0xFF ,
input . charCodeAt ( i ) & 0xFF ) ;
return output ;
}
/ *
* Convert a raw string to an array of little - endian words
* Characters > 255 have their high - byte silently ignored .
* /
function rstr2binl ( input )
{
var output = Array ( input . length >> 2 ) ;
for ( var i = 0 ; i < output . length ; i ++ )
output [ i ] = 0 ;
for ( var i = 0 ; i < input . length * 8 ; i += 8 )
output [ i >> 5 ] |= ( input . charCodeAt ( i / 8 ) & 0xFF ) << ( i % 32 ) ;
return output ;
}
/ *
* Convert an array of little - endian words to a string
* /
function binl2rstr ( input )
{
var output = "" ;
for ( var i = 0 ; i < input . length * 32 ; i += 8 )
output += String . fromCharCode ( ( input [ i >> 5 ] >>> ( i % 32 ) ) & 0xFF ) ;
return output ;
}
/ *
* Calculate the MD5 of an array of little - endian words , and a bit length .
* /
function binl _md5 ( x , len )
{
/* append padding */
x [ len >> 5 ] |= 0x80 << ( ( len ) % 32 ) ;
x [ ( ( ( len + 64 ) >>> 9 ) << 4 ) + 14 ] = len ;
var a = 1732584193 ;
var b = - 271733879 ;
var c = - 1732584194 ;
var d = 271733878 ;
for ( var i = 0 ; i < x . length ; i += 16 )
{
var olda = a ;
var oldb = b ;
var oldc = c ;
var oldd = d ;
a = md5 _ff ( a , b , c , d , x [ i + 0 ] , 7 , - 680876936 ) ;
d = md5 _ff ( d , a , b , c , x [ i + 1 ] , 12 , - 389564586 ) ;
c = md5 _ff ( c , d , a , b , x [ i + 2 ] , 17 , 606105819 ) ;
b = md5 _ff ( b , c , d , a , x [ i + 3 ] , 22 , - 1044525330 ) ;
a = md5 _ff ( a , b , c , d , x [ i + 4 ] , 7 , - 176418897 ) ;
d = md5 _ff ( d , a , b , c , x [ i + 5 ] , 12 , 1200080426 ) ;
c = md5 _ff ( c , d , a , b , x [ i + 6 ] , 17 , - 1473231341 ) ;
b = md5 _ff ( b , c , d , a , x [ i + 7 ] , 22 , - 45705983 ) ;
a = md5 _ff ( a , b , c , d , x [ i + 8 ] , 7 , 1770035416 ) ;
d = md5 _ff ( d , a , b , c , x [ i + 9 ] , 12 , - 1958414417 ) ;
c = md5 _ff ( c , d , a , b , x [ i + 10 ] , 17 , - 42063 ) ;
b = md5 _ff ( b , c , d , a , x [ i + 11 ] , 22 , - 1990404162 ) ;
a = md5 _ff ( a , b , c , d , x [ i + 12 ] , 7 , 1804603682 ) ;
d = md5 _ff ( d , a , b , c , x [ i + 13 ] , 12 , - 40341101 ) ;
c = md5 _ff ( c , d , a , b , x [ i + 14 ] , 17 , - 1502002290 ) ;
b = md5 _ff ( b , c , d , a , x [ i + 15 ] , 22 , 1236535329 ) ;
a = md5 _gg ( a , b , c , d , x [ i + 1 ] , 5 , - 165796510 ) ;
d = md5 _gg ( d , a , b , c , x [ i + 6 ] , 9 , - 1069501632 ) ;
c = md5 _gg ( c , d , a , b , x [ i + 11 ] , 14 , 643717713 ) ;
b = md5 _gg ( b , c , d , a , x [ i + 0 ] , 20 , - 373897302 ) ;
a = md5 _gg ( a , b , c , d , x [ i + 5 ] , 5 , - 701558691 ) ;
d = md5 _gg ( d , a , b , c , x [ i + 10 ] , 9 , 38016083 ) ;
c = md5 _gg ( c , d , a , b , x [ i + 15 ] , 14 , - 660478335 ) ;
b = md5 _gg ( b , c , d , a , x [ i + 4 ] , 20 , - 405537848 ) ;
a = md5 _gg ( a , b , c , d , x [ i + 9 ] , 5 , 568446438 ) ;
d = md5 _gg ( d , a , b , c , x [ i + 14 ] , 9 , - 1019803690 ) ;
c = md5 _gg ( c , d , a , b , x [ i + 3 ] , 14 , - 187363961 ) ;
b = md5 _gg ( b , c , d , a , x [ i + 8 ] , 20 , 1163531501 ) ;
a = md5 _gg ( a , b , c , d , x [ i + 13 ] , 5 , - 1444681467 ) ;
d = md5 _gg ( d , a , b , c , x [ i + 2 ] , 9 , - 51403784 ) ;
c = md5 _gg ( c , d , a , b , x [ i + 7 ] , 14 , 1735328473 ) ;
b = md5 _gg ( b , c , d , a , x [ i + 12 ] , 20 , - 1926607734 ) ;
a = md5 _hh ( a , b , c , d , x [ i + 5 ] , 4 , - 378558 ) ;
d = md5 _hh ( d , a , b , c , x [ i + 8 ] , 11 , - 2022574463 ) ;
c = md5 _hh ( c , d , a , b , x [ i + 11 ] , 16 , 1839030562 ) ;
b = md5 _hh ( b , c , d , a , x [ i + 14 ] , 23 , - 35309556 ) ;
a = md5 _hh ( a , b , c , d , x [ i + 1 ] , 4 , - 1530992060 ) ;
d = md5 _hh ( d , a , b , c , x [ i + 4 ] , 11 , 1272893353 ) ;
c = md5 _hh ( c , d , a , b , x [ i + 7 ] , 16 , - 155497632 ) ;
b = md5 _hh ( b , c , d , a , x [ i + 10 ] , 23 , - 1094730640 ) ;
a = md5 _hh ( a , b , c , d , x [ i + 13 ] , 4 , 681279174 ) ;
d = md5 _hh ( d , a , b , c , x [ i + 0 ] , 11 , - 358537222 ) ;
c = md5 _hh ( c , d , a , b , x [ i + 3 ] , 16 , - 722521979 ) ;
b = md5 _hh ( b , c , d , a , x [ i + 6 ] , 23 , 76029189 ) ;
a = md5 _hh ( a , b , c , d , x [ i + 9 ] , 4 , - 640364487 ) ;
d = md5 _hh ( d , a , b , c , x [ i + 12 ] , 11 , - 421815835 ) ;
c = md5 _hh ( c , d , a , b , x [ i + 15 ] , 16 , 530742520 ) ;
b = md5 _hh ( b , c , d , a , x [ i + 2 ] , 23 , - 995338651 ) ;
a = md5 _ii ( a , b , c , d , x [ i + 0 ] , 6 , - 198630844 ) ;
d = md5 _ii ( d , a , b , c , x [ i + 7 ] , 10 , 1126891415 ) ;
c = md5 _ii ( c , d , a , b , x [ i + 14 ] , 15 , - 1416354905 ) ;
b = md5 _ii ( b , c , d , a , x [ i + 5 ] , 21 , - 57434055 ) ;
a = md5 _ii ( a , b , c , d , x [ i + 12 ] , 6 , 1700485571 ) ;
d = md5 _ii ( d , a , b , c , x [ i + 3 ] , 10 , - 1894986606 ) ;
c = md5 _ii ( c , d , a , b , x [ i + 10 ] , 15 , - 1051523 ) ;
b = md5 _ii ( b , c , d , a , x [ i + 1 ] , 21 , - 2054922799 ) ;
a = md5 _ii ( a , b , c , d , x [ i + 8 ] , 6 , 1873313359 ) ;
d = md5 _ii ( d , a , b , c , x [ i + 15 ] , 10 , - 30611744 ) ;
c = md5 _ii ( c , d , a , b , x [ i + 6 ] , 15 , - 1560198380 ) ;
b = md5 _ii ( b , c , d , a , x [ i + 13 ] , 21 , 1309151649 ) ;
a = md5 _ii ( a , b , c , d , x [ i + 4 ] , 6 , - 145523070 ) ;
d = md5 _ii ( d , a , b , c , x [ i + 11 ] , 10 , - 1120210379 ) ;
c = md5 _ii ( c , d , a , b , x [ i + 2 ] , 15 , 718787259 ) ;
b = md5 _ii ( b , c , d , a , x [ i + 9 ] , 21 , - 343485551 ) ;
a = safe _add ( a , olda ) ;
b = safe _add ( b , oldb ) ;
c = safe _add ( c , oldc ) ;
d = safe _add ( d , oldd ) ;
}
return Array ( a , b , c , d ) ;
}
/ *
* These functions implement the four basic operations the algorithm uses .
* /
function md5 _cmn ( q , a , b , x , s , t )
{
return safe _add ( bit _rol ( safe _add ( safe _add ( a , q ) , safe _add ( x , t ) ) , s ) , b ) ;
}
function md5 _ff ( a , b , c , d , x , s , t )
{
return md5 _cmn ( ( b & c ) | ( ( ~ b ) & d ) , a , b , x , s , t ) ;
}
function md5 _gg ( a , b , c , d , x , s , t )
{
return md5 _cmn ( ( b & d ) | ( c & ( ~ d ) ) , a , b , x , s , t ) ;
}
function md5 _hh ( a , b , c , d , x , s , t )
{
return md5 _cmn ( b ^ c ^ d , a , b , x , s , t ) ;
}
function md5 _ii ( a , b , c , d , x , s , t )
{
return md5 _cmn ( c ^ ( b | ( ~ d ) ) , a , b , x , s , t ) ;
}
/ *
* Add integers , wrapping at 2 ^ 32. This uses 16 - bit operations internally
* to work around bugs in some JS interpreters .
* /
function safe _add ( x , y )
{
var lsw = ( x & 0xFFFF ) + ( y & 0xFFFF ) ;
var msw = ( x >> 16 ) + ( y >> 16 ) + ( lsw >> 16 ) ;
return ( msw << 16 ) | ( lsw & 0xFFFF ) ;
}
/ *
* Bitwise rotate a 32 - bit number to the left .
* /
function bit _rol ( num , cnt )
{
return ( num << cnt ) | ( num >>> ( 32 - cnt ) ) ;
}
/ * # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
UTF - 8 Decoder and Encoder
base64 Encoder and Decoder
written by Tobias Kieslich , justdreams
Contact : tobias @ justdreams . de http : //www.justdreams.de/
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # * /
// returns an array of byterepresenting dezimal numbers which represent the
// plaintext in an UTF-8 encoded version. Expects a string.
// This function includes an exception management for those nasty browsers like
// NN401, which returns negative decimal numbers for chars>128. I hate it!!
// This handling is unfortunately limited to the user's charset. Anyway, it works
// in most of the cases! Special signs with an unicode>256 return numbers, which
// can not be converted to the actual unicode and so not to the valid utf-8
// representation. Anyway, this function does always return values which can not
// misinterpretd by RC4 or base64 en- or decoding, because every value is >0 and
// <255!!
// Arrays are faster and easier to handle in b64 encoding or encrypting....
function utf8t2d ( t )
{
t = t . replace ( /\r\n/g , "\n" ) ;
var d = new Array ; var test = String . fromCharCode ( 237 ) ;
if ( test . charCodeAt ( 0 ) < 0 )
for ( var n = 0 ; n < t . length ; n ++ )
{
var c = t . charCodeAt ( n ) ;
if ( c > 0 )
d [ d . length ] = c ;
else {
d [ d . length ] = ( ( ( 256 + c ) >> 6 ) | 192 ) ;
d [ d . length ] = ( ( ( 256 + c ) & 63 ) | 128 ) ; }
}
else
for ( var n = 0 ; n < t . length ; n ++ )
{
var c = t . charCodeAt ( n ) ;
// all the signs of asci => 1byte
if ( c < 128 )
d [ d . length ] = c ;
// all the signs between 127 and 2047 => 2byte
else if ( ( c > 127 ) && ( c < 2048 ) ) {
d [ d . length ] = ( ( c >> 6 ) | 192 ) ;
d [ d . length ] = ( ( c & 63 ) | 128 ) ; }
// all the signs between 2048 and 66536 => 3byte
else {
d [ d . length ] = ( ( c >> 12 ) | 224 ) ;
d [ d . length ] = ( ( ( c >> 6 ) & 63 ) | 128 ) ;
d [ d . length ] = ( ( c & 63 ) | 128 ) ; }
}
return d ;
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
// returns plaintext from an array of bytesrepresenting dezimal numbers, which
// represent an UTF-8 encoded text; browser which does not understand unicode
// like NN401 will show "?"-signs instead
// expects an array of byterepresenting decimals; returns a string
function utf8d2t ( d )
{
var r = new Array ; var i = 0 ;
while ( i < d . length )
{
if ( d [ i ] < 128 ) {
r [ r . length ] = String . fromCharCode ( d [ i ] ) ; i ++ ; }
else if ( ( d [ i ] > 191 ) && ( d [ i ] < 224 ) ) {
r [ r . length ] = String . fromCharCode ( ( ( d [ i ] & 31 ) << 6 ) | ( d [ i + 1 ] & 63 ) ) ; i += 2 ; }
else {
r [ r . length ] = String . fromCharCode ( ( ( d [ i ] & 15 ) << 12 ) | ( ( d [ i + 1 ] & 63 ) << 6 ) | ( d [ i + 2 ] & 63 ) ) ; i += 3 ; }
}
return r . join ( "" ) ;
}
// included in <body onload="b64arrays"> it creates two arrays which makes base64
// en- and decoding faster
// this speed is noticeable especially when coding larger texts (>5k or so)
function b64arrays ( ) {
var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' ;
b64 = new Array ( ) ; f64 = new Array ( ) ;
for ( var i = 0 ; i < b64s . length ; i ++ ) {
b64 [ i ] = b64s . charAt ( i ) ;
f64 [ b64s . charAt ( i ) ] = i ;
}
}
// creates a base64 encoded text out of an array of byerepresenting dezimals
// it is really base64 :) this makes serversided handling easier
// expects an array; returns a string
function b64d2t ( d ) {
var r = new Array ; var i = 0 ; var dl = d . length ;
// this is for the padding
if ( ( dl % 3 ) == 1 ) {
d [ d . length ] = 0 ; d [ d . length ] = 0 ; }
if ( ( dl % 3 ) == 2 )
d [ d . length ] = 0 ;
// from here conversion
while ( i < d . length )
{
r [ r . length ] = b64 [ d [ i ] >> 2 ] ;
r [ r . length ] = b64 [ ( ( d [ i ] & 3 ) << 4 ) | ( d [ i + 1 ] >> 4 ) ] ;
r [ r . length ] = b64 [ ( ( d [ i + 1 ] & 15 ) << 2 ) | ( d [ i + 2 ] >> 6 ) ] ;
r [ r . length ] = b64 [ d [ i + 2 ] & 63 ] ;
i += 3 ;
}
// this is again for the padding
if ( ( dl % 3 ) == 1 )
r [ r . length - 1 ] = r [ r . length - 2 ] = "=" ;
if ( ( dl % 3 ) == 2 )
r [ r . length - 1 ] = "=" ;
// we join the array to return a textstring
var t = r . join ( "" ) ;
return t ;
}
// returns array of byterepresenting numbers created of an base64 encoded text
// it is still the slowest function in this modul; I hope I can make it faster
// expects string; returns an array
function b64t2d ( t ) {
var d = new Array ; var i = 0 ;
// here we fix this CRLF sequenz created by MS-OS; arrrgh!!!
t = t . replace ( /\n|\r/g , "" ) ; t = t . replace ( /=/g , "" ) ;
while ( i < t . length )
{
d [ d . length ] = ( f64 [ t . charAt ( i ) ] << 2 ) | ( f64 [ t . charAt ( i + 1 ) ] >> 4 ) ;
d [ d . length ] = ( ( ( f64 [ t . charAt ( i + 1 ) ] & 15 ) << 4 ) | ( f64 [ t . charAt ( i + 2 ) ] >> 2 ) ) ;
d [ d . length ] = ( ( ( f64 [ t . charAt ( i + 2 ) ] & 3 ) << 6 ) | ( f64 [ t . charAt ( i + 3 ) ] ) ) ;
i += 4 ;
}
if ( t . length % 4 == 2 )
d = d . slice ( 0 , d . length - 2 ) ;
if ( t . length % 4 == 3 )
d = d . slice ( 0 , d . length - 1 ) ;
return d ;
}
if ( typeof ( atob ) == 'undefined' || typeof ( btoa ) == 'undefined' )
b64arrays ( ) ;
if ( typeof ( atob ) == 'undefined' ) {
b64decode = function ( s ) {
return utf8d2t ( b64t2d ( s ) ) ;
}
} else {
b64decode = function ( s ) {
return decodeURIComponent ( escape ( atob ( s ) ) ) ;
}
}
if ( typeof ( btoa ) == 'undefined' ) {
b64encode = function ( s ) {
return b64d2t ( utf8t2d ( s ) ) ;
}
} else {
b64encode = function ( s ) {
return btoa ( unescape ( encodeURIComponent ( s ) ) ) ;
}
}
function cnonce ( size ) {
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ;
var cnonce = '' ;
for ( var i = 0 ; i < size ; i ++ ) {
cnonce += tab . charAt ( Math . round ( Math . random ( new Date ( ) . getTime ( ) ) * ( tab . length - 1 ) ) ) ;
}
return cnonce ;
}
JSJAC _HAVEKEYS = true ; // whether to use keys
JSJAC _NKEYS = 16 ; // number of keys to generate
2014-04-08 20:14:28 +02:00
2014-11-25 23:42:38 +01:00
JSJAC _INACTIVITY = 300 ; // qnd hack to make suspend/resume
2014-03-12 14:52:47 +01:00
// work more smoothly with polling
JSJAC _ERR _COUNT = 10 ; // number of retries in case of connection
// errors
JSJAC _ALLOW _PLAIN = true ; // whether to allow plaintext logins
JSJAC _CHECKQUEUEINTERVAL = 100 ; // msecs to poll send queue
JSJAC _CHECKINQUEUEINTERVAL = 100 ; // msecs to poll incoming queue
JSJAC _TIMERVAL = 2000 ; // default polling interval
JSJAC _ALLOW _PLAIN = true ; // whether to allow plaintext logins
JSJAC _ALLOW _SCRAM = false ; // allow usage of SCRAM-SHA-1 authentication; please note that it is quite slow so it is disable by default
2014-04-08 20:14:28 +02:00
JSJAC _RETRYDELAY = 5000 ; // msecs to wait before trying next
// request after error
JSJAC _REGID _TIMEOUT = 20000 ; // time in msec until registered
// callbacks for ids timeout
/* Options specific to HTTP Binding (BOSH) */
JSJACHBC _MAX _HOLD = 1 ; // default for number of connctions
// held by connection manager
2014-11-25 23:42:38 +01:00
JSJACHBC _MAX _WAIT = 20 ; // default 'wait' param - how long an
2014-04-08 20:14:28 +02:00
// idle connection should be held by
// connection manager
2014-03-12 14:52:47 +01:00
JSJACHBC _BOSH _VERSION = "1.6" ;
JSJACHBC _USE _BOSH _VER = true ;
JSJACHBC _MAXPAUSE = 20 ; // how long a suspend/resume cycle may take
/*** END CONFIG ***/
/ * C o p y r i g h t ( c ) 2 0 0 5 - 2 0 0 7 S a m S t e p h e n s o n
*
* Permission is hereby granted , free of charge , to any person
* obtaining a copy of this software and associated documentation
* files ( the "Software" ) , to deal in the Software without
* restriction , including without limitation the rights to use , copy ,
* modify , merge , publish , distribute , sublicense , and / or sell copies
* of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
*
* THE SOFTWARE IS PROVIDED "AS IS" , WITHOUT WARRANTY OF ANY KIND ,
* EXPRESS OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT . IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER LIABILITY , WHETHER IN AN
* ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM , OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE .
* /
/ *
json . js
taken from prototype . js , made static
* /
function JSJaCJSON ( ) { }
JSJaCJSON . toString = function ( obj ) {
var m = {
'\b' : '\\b' ,
'\t' : '\\t' ,
'\n' : '\\n' ,
'\f' : '\\f' ,
'\r' : '\\r' ,
'"' : '\\"' ,
'\\' : '\\\\'
} ,
s = {
array : function ( x ) {
var a = [ '[' ] , b , f , i , l = x . length , v ;
for ( i = 0 ; i < l ; i += 1 ) {
v = x [ i ] ;
f = s [ typeof v ] ;
if ( f ) {
2014-04-08 20:14:28 +02:00
try {
2014-03-12 14:52:47 +01:00
v = f ( v ) ;
if ( typeof v == 'string' ) {
if ( b ) {
a [ a . length ] = ',' ;
}
a [ a . length ] = v ;
b = true ;
}
2014-11-25 23:42:38 +01:00
} catch ( e ) {
2014-04-08 20:14:28 +02:00
}
2014-03-12 14:52:47 +01:00
}
}
a [ a . length ] = ']' ;
return a . join ( '' ) ;
} ,
'boolean' : function ( x ) {
return String ( x ) ;
} ,
'null' : function ( x ) {
return "null" ;
} ,
number : function ( x ) {
return isFinite ( x ) ? String ( x ) : 'null' ;
} ,
object : function ( x ) {
if ( x ) {
if ( x instanceof Array ) {
return s . array ( x ) ;
}
var a = [ '{' ] , b , f , i , v ;
for ( i in x ) {
if ( x . hasOwnProperty ( i ) ) {
v = x [ i ] ;
f = s [ typeof v ] ;
if ( f ) {
2014-04-08 20:14:28 +02:00
try {
2014-03-12 14:52:47 +01:00
v = f ( v ) ;
if ( typeof v == 'string' ) {
if ( b ) {
a [ a . length ] = ',' ;
}
a . push ( s . string ( i ) , ':' , v ) ;
b = true ;
}
2014-04-08 20:14:28 +02:00
} catch ( e ) {
}
2014-03-12 14:52:47 +01:00
}
}
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
a [ a . length ] = '}' ;
return a . join ( '' ) ;
}
return 'null' ;
} ,
string : function ( x ) {
if ( /["\\\x00-\x1f]/ . test ( x ) ) {
x = x . replace ( /([\x00-\x1f\\"])/g , function ( a , b ) {
var c = m [ b ] ;
if ( c ) {
return c ;
}
c = b . charCodeAt ( ) ;
return '\\u00' +
Math . floor ( c / 16 ) . toString ( 16 ) +
( c % 16 ) . toString ( 16 ) ;
} ) ;
}
return '"' + x + '"' ;
}
} ;
switch ( typeof ( obj ) ) {
case 'object' :
return s . object ( obj ) ;
case 'array' :
return s . array ( obj ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
}
} ;
JSJaCJSON . parse = function ( str ) {
try {
return ! ( /[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/ . test (
str . replace ( /"(\\.|[^"\\])*"/g , '' ) ) ) &&
eval ( '(' + str + ')' ) ;
} catch ( e ) {
return false ;
}
} ;
/ * *
* @ fileoverview This file contains all things that make life easier when
* dealing with JIDs
* @ author Stefan Strigler
2014-11-25 20:12:58 +01:00
* @ version 1.3
2014-03-12 14:52:47 +01:00
* /
/ * *
* list of forbidden chars for nodenames
* @ private
* /
var JSJACJID _FORBIDDEN = [ '"' , ' ' , '&' , '\'' , '/' , ':' , '<' , '>' , '@' ] ;
/ * *
* Creates a new JSJaCJID object
* @ class JSJaCJID models xmpp jid objects
* @ constructor
* @ param { Object } jid jid may be either of type String or a JID represented
* by JSON with fields 'node' , 'domain' and 'resource'
* @ throws JSJaCJIDInvalidException Thrown if jid is not valid
* @ return a new JSJaCJID object
* /
function JSJaCJID ( jid ) {
/ * *
* @ private
* /
this . _node = '' ;
/ * *
* @ private
* /
this . _domain = '' ;
/ * *
* @ private
* /
this . _resource = '' ;
if ( typeof ( jid ) == 'string' ) {
if ( jid . indexOf ( '@' ) != - 1 ) {
this . setNode ( jid . substring ( 0 , jid . indexOf ( '@' ) ) ) ;
jid = jid . substring ( jid . indexOf ( '@' ) + 1 ) ;
}
if ( jid . indexOf ( '/' ) != - 1 ) {
this . setResource ( jid . substring ( jid . indexOf ( '/' ) + 1 ) ) ;
jid = jid . substring ( 0 , jid . indexOf ( '/' ) ) ;
}
this . setDomain ( jid ) ;
} else {
this . setNode ( jid . node ) ;
this . setDomain ( jid . domain ) ;
this . setResource ( jid . resource ) ;
}
}
/ * *
* Gets the node part of the jid
* @ return A string representing the node name
* @ type String
* /
JSJaCJID . prototype . getNode = function ( ) { return this . _node ; } ;
/ * *
* Gets the domain part of the jid
* @ return A string representing the domain name
* @ type String
* /
JSJaCJID . prototype . getDomain = function ( ) { return this . _domain ; } ;
/ * *
* Gets the resource part of the jid
* @ return A string representing the resource
* @ type String
* /
JSJaCJID . prototype . getResource = function ( ) { return this . _resource ; } ;
/ * *
* Sets the node part of the jid
* @ param { String } node Name of the node
* @ throws JSJaCJIDInvalidException Thrown if node name contains invalid chars
* @ return This object
* @ type JSJaCJID
* /
JSJaCJID . prototype . setNode = function ( node ) {
JSJaCJID . _checkNodeName ( node ) ;
this . _node = node || '' ;
return this ;
} ;
/ * *
* Sets the domain part of the jid
* @ param { String } domain Name of the domain
* @ throws JSJaCJIDInvalidException Thrown if domain name contains invalid
* chars or is empty
* @ return This object
* @ type JSJaCJID
* /
JSJaCJID . prototype . setDomain = function ( domain ) {
if ( ! domain || domain == '' )
throw new JSJaCJIDInvalidException ( "domain name missing" ) ;
// chars forbidden for a node are not allowed in domain names
// anyway, so let's check
JSJaCJID . _checkNodeName ( domain ) ;
this . _domain = domain ;
return this ;
} ;
/ * *
* Sets the resource part of the jid
* @ param { String } resource Name of the resource
* @ return This object
* @ type JSJaCJID
* /
JSJaCJID . prototype . setResource = function ( resource ) {
this . _resource = resource || '' ;
return this ;
} ;
/ * *
* The string representation of the full jid
* @ return A string representing the jid
* @ type String
* /
JSJaCJID . prototype . toString = function ( ) {
var jid = '' ;
if ( this . getNode ( ) && this . getNode ( ) != '' )
jid = this . getNode ( ) + '@' ;
jid += this . getDomain ( ) ; // we always have a domain
if ( this . getResource ( ) && this . getResource ( ) != "" )
jid += '/' + this . getResource ( ) ;
return jid ;
} ;
/ * *
* Removes the resource part of the jid
* @ return This object
* @ type JSJaCJID
* /
JSJaCJID . prototype . removeResource = function ( ) {
return this . setResource ( ) ;
} ;
/ * *
* creates a copy of this JSJaCJID object
* @ return A copy of this
* @ type JSJaCJID
* /
JSJaCJID . prototype . clone = function ( ) {
return new JSJaCJID ( this . toString ( ) ) ;
} ;
/ * *
* Compares two jids if they belong to the same entity ( i . e . w / o resource )
* @ param { String } jid a jid as string or JSJaCJID object
* @ return 'true' if jid is same entity as this
* @ type Boolean
* /
JSJaCJID . prototype . isEntity = function ( jid ) {
if ( typeof jid == 'string' )
2014-04-08 20:14:28 +02:00
jid = ( new JSJaCJID ( jid ) ) ;
2014-03-12 14:52:47 +01:00
jid . removeResource ( ) ;
return ( this . clone ( ) . removeResource ( ) . toString ( ) === jid . toString ( ) ) ;
} ;
/ * *
* Check if node name is valid
* @ private
* @ param { String } node A name for a node
* @ throws JSJaCJIDInvalidException Thrown if name for node is not allowed
* /
JSJaCJID . _checkNodeName = function ( nodeprep ) {
if ( ! nodeprep || nodeprep == '' )
return ;
for ( var i = 0 ; i < JSJACJID _FORBIDDEN . length ; i ++ ) {
if ( nodeprep . indexOf ( JSJACJID _FORBIDDEN [ i ] ) != - 1 ) {
throw new JSJaCJIDInvalidException ( "forbidden char in nodename: " + JSJACJID _FORBIDDEN [ i ] ) ;
}
}
} ;
/ * *
* Creates a new Exception of type JSJaCJIDInvalidException
* @ class Exception to indicate invalid values for a jid
* @ constructor
* @ param { String } message The message associated with this Exception
* /
function JSJaCJIDInvalidException ( message ) {
/ * *
* The exceptions associated message
* @ type String
* /
this . message = message ;
/ * *
* The name of the exception
* @ type String
* /
this . name = "JSJaCJIDInvalidException" ;
}
/* Copyright (c) 2005 Thomas Fuchs (http:/ / script . aculo . us , http : //mir.aculo.us)
*
* Permission is hereby granted , free of charge , to any person
* obtaining a copy of this software and associated documentation
* files ( the "Software" ) , to deal in the Software without
* restriction , including without limitation the rights to use , copy ,
* modify , merge , publish , distribute , sublicense , and / or sell copies
* of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED "AS IS" , WITHOUT WARRANTY OF ANY KIND ,
* EXPRESS OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT . IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER LIABILITY , WHETHER IN AN
* ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM , OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE .
* /
/ * *
* @ private
* This code is taken from { @ link
* http : //wiki.script.aculo.us/scriptaculous/show/Builder
* script . aculo . us ' Dom Builder } and has been modified to suit our
* needs . < br / >
* The original parts of the code do have the following
* copyright and license notice : < br / >
* Copyright ( c ) 2005 , 2006 Thomas Fuchs ( http : //script.aculo.us,
* http : //mir.acu lo.us) <br/>
* script . aculo . us is freely distributable under the terms of an
* MIT - style license . < br >
* For details , see the script . aculo . us web site :
* http : //script.aculo.us/<br>
* /
var JSJaCBuilder = {
/ * *
* @ private
* /
buildNode : function ( doc , elementName ) {
var element , ns = arguments [ 4 ] ;
// attributes (or text)
if ( arguments [ 2 ] )
if ( JSJaCBuilder . _isStringOrNumber ( arguments [ 2 ] ) ||
( arguments [ 2 ] instanceof Array ) ) {
element = this . _createElement ( doc , elementName , ns ) ;
JSJaCBuilder . _children ( doc , element , arguments [ 2 ] ) ;
} else {
ns = arguments [ 2 ] [ 'xmlns' ] || ns ;
element = this . _createElement ( doc , elementName , ns ) ;
for ( attr in arguments [ 2 ] ) {
if ( arguments [ 2 ] . hasOwnProperty ( attr ) && attr != 'xmlns' )
element . setAttribute ( attr , arguments [ 2 ] [ attr ] ) ;
}
}
else
element = this . _createElement ( doc , elementName , ns ) ;
// text, or array of children
if ( arguments [ 3 ] )
JSJaCBuilder . _children ( doc , element , arguments [ 3 ] , ns ) ;
return element ;
} ,
_createElement : function ( doc , elementName , ns ) {
try {
if ( ns )
return doc . createElementNS ( ns , elementName ) ;
} catch ( ex ) { }
var el = doc . createElement ( elementName ) ;
if ( ns )
el . setAttribute ( "xmlns" , ns ) ;
return el ;
} ,
/ * *
* @ private
* /
_text : function ( doc , text ) {
return doc . createTextNode ( text ) ;
} ,
/ * *
* @ private
* /
_children : function ( doc , element , children , ns ) {
if ( typeof children == 'object' ) { // array can hold nodes and text
for ( var i in children ) {
if ( children . hasOwnProperty ( i ) ) {
var e = children [ i ] ;
if ( typeof e == 'object' ) {
if ( e instanceof Array ) {
var node = JSJaCBuilder . buildNode ( doc , e [ 0 ] , e [ 1 ] , e [ 2 ] , ns ) ;
element . appendChild ( node ) ;
} else {
element . appendChild ( e ) ;
}
} else {
if ( JSJaCBuilder . _isStringOrNumber ( e ) ) {
element . appendChild ( JSJaCBuilder . _text ( doc , e ) ) ;
}
}
}
}
} else {
if ( JSJaCBuilder . _isStringOrNumber ( children ) ) {
element . appendChild ( JSJaCBuilder . _text ( doc , children ) ) ;
}
}
} ,
_attributes : function ( attributes ) {
var attrs = [ ] ;
for ( attribute in attributes )
if ( attributes . hasOwnProperty ( attribute ) )
attrs . push ( attribute +
'="' + attributes [ attribute ] . toString ( ) . htmlEnc ( ) + '"' ) ;
return attrs . join ( " " ) ;
} ,
_isStringOrNumber : function ( param ) {
return ( typeof param == 'string' || typeof param == 'number' ) ;
}
} ;
/ * *
* @ fileoverview Contains all Jabber / XMPP packet related classes .
* @ author Stefan Strigler steve @ zeank . in - berlin . de
2014-11-25 20:12:58 +01:00
* @ version 1.3
2014-03-12 14:52:47 +01:00
* /
var JSJACPACKET _USE _XMLNS = true ;
/ * *
* Creates a new packet with given root tag name ( for internal use )
* @ class Somewhat abstract base class for all kinds of specialised packets
* @ param { String } name The root tag name of the packet
* ( i . e . one of 'message' , 'iq' or 'presence' )
* /
function JSJaCPacket ( name ) {
/ * *
* @ private
* /
this . name = name ;
if ( typeof ( JSJACPACKET _USE _XMLNS ) != 'undefined' && JSJACPACKET _USE _XMLNS )
/ * *
* @ private
* /
this . doc = XmlDocument . create ( name , 'jabber:client' ) ;
else
/ * *
* @ private
* /
this . doc = XmlDocument . create ( name , '' ) ;
}
/ * *
* Gets the type ( name of root element ) of this packet , i . e . one of
* 'presence' , 'message' or 'iq'
* @ return the top level tag name
* @ type String
* /
JSJaCPacket . prototype . pType = function ( ) { return this . name ; } ;
/ * *
* Gets the associated Document for this packet .
* @ type { @ link http : //www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#i-Document Document}
* /
JSJaCPacket . prototype . getDoc = function ( ) {
return this . doc ;
} ;
/ * *
* Gets the root node of this packet
* @ type { @ link http : //www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
* /
JSJaCPacket . prototype . getNode = function ( ) {
if ( this . getDoc ( ) && this . getDoc ( ) . documentElement )
return this . getDoc ( ) . documentElement ;
else
return null ;
} ;
/ * *
* Sets the 'to' attribute of the root node of this packet
* @ param { String } to
* @ type JSJaCPacket
* /
JSJaCPacket . prototype . setTo = function ( to ) {
if ( ! to || to == '' )
this . getNode ( ) . removeAttribute ( 'to' ) ;
else if ( typeof ( to ) == 'string' )
this . getNode ( ) . setAttribute ( 'to' , to ) ;
else
this . getNode ( ) . setAttribute ( 'to' , to . toString ( ) ) ;
return this ;
} ;
/ * *
* Sets the 'from' attribute of the root node of this
* packet . Usually this is not needed as the server will take care
* of this automatically .
* @ type JSJaCPacket
* /
JSJaCPacket . prototype . setFrom = function ( from ) {
if ( ! from || from == '' )
this . getNode ( ) . removeAttribute ( 'from' ) ;
else if ( typeof ( from ) == 'string' )
this . getNode ( ) . setAttribute ( 'from' , from ) ;
else
this . getNode ( ) . setAttribute ( 'from' , from . toString ( ) ) ;
return this ;
} ;
/ * *
* Sets 'id' attribute of the root node of this packet .
* @ param { String } id The id of the packet .
* @ type JSJaCPacket
* /
JSJaCPacket . prototype . setID = function ( id ) {
if ( ! id || id == '' )
this . getNode ( ) . removeAttribute ( 'id' ) ;
else
this . getNode ( ) . setAttribute ( 'id' , id ) ;
return this ;
} ;
/ * *
* Sets the 'type' attribute of the root node of this packet .
* @ param { String } type The type of the packet .
* @ type JSJaCPacket
* /
JSJaCPacket . prototype . setType = function ( type ) {
if ( ! type || type == '' )
this . getNode ( ) . removeAttribute ( 'type' ) ;
else
this . getNode ( ) . setAttribute ( 'type' , type ) ;
return this ;
} ;
/ * *
* Sets 'xml:lang' for this packet
* @ param { String } xmllang The xml : lang of the packet .
* @ type JSJaCPacket
* /
JSJaCPacket . prototype . setXMLLang = function ( xmllang ) {
// Fix IE bug with xml:lang attribute
// Also due to issues with both BD and jQuery being used, employ a simple regexp since the detection
// here is very limited.
if ( navigator . appVersion . match ( /^.*MSIE (\d)/ ) || navigator . userAgent . match ( /Trident\/(\d+)((\.)(\d+))?/ ) )
return this ;
if ( ! xmllang || xmllang == '' )
this . getNode ( ) . removeAttribute ( 'xml:lang' ) ;
else
this . getNode ( ) . setAttribute ( 'xml:lang' , xmllang ) ;
return this ;
} ;
/ * *
* Gets the 'to' attribute of this packet
* @ type String
* /
JSJaCPacket . prototype . getTo = function ( ) {
return this . getNode ( ) . getAttribute ( 'to' ) ;
} ;
/ * *
* Gets the 'from' attribute of this packet .
* @ type String
* /
JSJaCPacket . prototype . getFrom = function ( ) {
return this . getNode ( ) . getAttribute ( 'from' ) ;
} ;
/ * *
* Gets the 'to' attribute of this packet as a JSJaCJID object
* @ type JSJaCJID
* /
JSJaCPacket . prototype . getToJID = function ( ) {
return new JSJaCJID ( this . getTo ( ) ) ;
} ;
/ * *
* Gets the 'from' attribute of this packet as a JSJaCJID object
* @ type JSJaCJID
* /
JSJaCPacket . prototype . getFromJID = function ( ) {
return new JSJaCJID ( this . getFrom ( ) ) ;
} ;
/ * *
* Gets the 'id' of this packet
* @ type String
* /
JSJaCPacket . prototype . getID = function ( ) {
return this . getNode ( ) . getAttribute ( 'id' ) ;
} ;
/ * *
* Gets the 'type' of this packet
* @ type String
* /
JSJaCPacket . prototype . getType = function ( ) {
return this . getNode ( ) . getAttribute ( 'type' ) ;
} ;
/ * *
* Gets the 'xml:lang' of this packet
* @ type String
* /
JSJaCPacket . prototype . getXMLLang = function ( ) {
return this . getNode ( ) . getAttribute ( 'xml:lang' ) ;
} ;
/ * *
* Gets the 'xmlns' ( xml namespace ) of the root node of this packet
* @ type String
* /
JSJaCPacket . prototype . getXMLNS = function ( ) {
return this . getNode ( ) . namespaceURI || this . getNode ( ) . getAttribute ( 'xmlns' ) ;
} ;
/ * *
* Gets a child element of this packet . If no params given returns first child .
* @ param { String } name Tagname of child to retrieve . Use '*' to match any tag . [ optional ]
* @ param { String } ns Namespace of child . Use '*' to match any ns . [ optional ]
* @ return The child node , null if none found
* @ type { @ link http : //www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
* /
JSJaCPacket . prototype . getChild = function ( name , ns ) {
if ( ! this . getNode ( ) ) {
return null ;
}
name = name || '*' ;
ns = ns || '*' ;
if ( this . getNode ( ) . getElementsByTagNameNS ) {
return this . getNode ( ) . getElementsByTagNameNS ( ns , name ) . item ( 0 ) ;
}
// fallback
var nodes = this . getNode ( ) . getElementsByTagName ( name ) ;
if ( ns != '*' ) {
for ( var i = 0 ; i < nodes . length ; i ++ ) {
if ( nodes . item ( i ) . namespaceURI == ns || nodes . item ( i ) . getAttribute ( 'xmlns' ) == ns ) {
return nodes . item ( i ) ;
}
}
} else {
return nodes . item ( 0 ) ;
}
return null ; // nothing found
} ;
/ * *
* Gets the node value of a child element of this packet .
* @ param { String } name Tagname of child to retrieve .
* @ param { String } ns Namespace of child
* @ return The value of the child node , empty string if none found
* @ type String
* /
JSJaCPacket . prototype . getChildVal = function ( name , ns ) {
var node = this . getChild ( name , ns ) ;
var ret = '' ;
if ( node && node . hasChildNodes ( ) ) {
// concatenate all values from childNodes
for ( var i = 0 ; i < node . childNodes . length ; i ++ )
if ( node . childNodes . item ( i ) . nodeValue )
ret += node . childNodes . item ( i ) . nodeValue ;
}
return ret ;
} ;
/ * *
* Returns a copy of this node
* @ return a copy of this node
* @ type JSJaCPacket
* /
JSJaCPacket . prototype . clone = function ( ) {
return JSJaCPacket . wrapNode ( this . getNode ( ) ) ;
} ;
/ * *
* Checks if packet is of type 'error'
* @ return 'true' if this packet is of type 'error' , 'false' otherwise
* @ type boolean
* /
JSJaCPacket . prototype . isError = function ( ) {
return ( this . getType ( ) == 'error' ) ;
} ;
/ * *
* 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.
* @ param { STANZA _ERROR } stanza _error an error stanza containing error cody , type and condition of the error to be indicated
* @ return an error reply packet
* @ type JSJaCPacket
* /
JSJaCPacket . prototype . errorReply = function ( stanza _error ) {
var rPacket = this . clone ( ) ;
rPacket . setTo ( this . getFrom ( ) ) ;
rPacket . setFrom ( ) ;
rPacket . setType ( 'error' ) ;
rPacket . appendNode ( 'error' ,
{ code : stanza _error . code , type : stanza _error . type } ,
[ [ stanza _error . cond ] ] ) ;
return rPacket ;
} ;
/ * *
* Returns a string representation of the raw xml content of this packet .
* @ type String
* /
JSJaCPacket . prototype . xml = typeof XMLSerializer != 'undefined' ?
function ( ) {
var r = ( new XMLSerializer ( ) ) . serializeToString ( this . getNode ( ) ) ;
if ( typeof ( r ) == 'undefined' )
r = ( new XMLSerializer ( ) ) . serializeToString ( this . doc ) ; // oldschool
return r
} :
function ( ) { // IE
return this . getDoc ( ) . xml
} ;
// PRIVATE METHODS DOWN HERE
/ * *
* Gets an attribute of the root element
* @ private
* /
JSJaCPacket . prototype . _getAttribute = function ( attr ) {
return this . getNode ( ) . getAttribute ( attr ) ;
} ;
if ( document . ELEMENT _NODE == null ) {
document . ELEMENT _NODE = 1 ;
document . ATTRIBUTE _NODE = 2 ;
document . TEXT _NODE = 3 ;
document . CDATA _SECTION _NODE = 4 ;
document . ENTITY _REFERENCE _NODE = 5 ;
document . ENTITY _NODE = 6 ;
document . PROCESSING _INSTRUCTION _NODE = 7 ;
document . COMMENT _NODE = 8 ;
document . DOCUMENT _NODE = 9 ;
document . DOCUMENT _TYPE _NODE = 10 ;
document . DOCUMENT _FRAGMENT _NODE = 11 ;
document . NOTATION _NODE = 12 ;
}
/ * *
* import node into this packets document
* @ private
* /
JSJaCPacket . prototype . _importNode = function ( node , allChildren ) {
switch ( node . nodeType ) {
case document . ELEMENT _NODE :
if ( this . getDoc ( ) . createElementNS ) {
var newNode = this . getDoc ( ) . createElementNS ( node . namespaceURI , node . nodeName ) ;
} else {
var newNode = this . getDoc ( ) . createElement ( node . nodeName ) ;
}
/* does the node have any attributes to add? */
if ( node . attributes && node . attributes . length > 0 )
for ( var i = 0 , il = node . attributes . length ; i < il ; i ++ ) {
var attr = node . attributes . item ( i ) ;
if ( attr . nodeName == 'xmlns' && newNode . getAttribute ( 'xmlns' ) != null ) continue ;
if ( newNode . setAttributeNS && attr . namespaceURI ) {
newNode . setAttributeNS ( attr . namespaceURI ,
attr . nodeName ,
attr . nodeValue ) ;
} else {
newNode . setAttribute ( attr . nodeName ,
attr . nodeValue ) ;
}
}
/* are we going after children too, and does the node have any? */
if ( allChildren && node . childNodes && node . childNodes . length > 0 ) {
for ( var i = 0 , il = node . childNodes . length ; i < il ; i ++ ) {
newNode . appendChild ( this . _importNode ( node . childNodes . item ( i ) , allChildren ) ) ;
}
}
return newNode ;
break ;
case document . TEXT _NODE :
case document . CDATA _SECTION _NODE :
case document . COMMENT _NODE :
return this . getDoc ( ) . createTextNode ( node . nodeValue ) ;
break ;
}
} ;
/ * *
* Set node value of a child node
* @ private
* /
JSJaCPacket . prototype . _setChildNode = function ( nodeName , nodeValue ) {
var aNode = this . getChild ( nodeName ) ;
var tNode = this . getDoc ( ) . createTextNode ( nodeValue ) ;
if ( aNode )
try {
aNode . replaceChild ( tNode , aNode . firstChild ) ;
} catch ( e ) { }
else {
try {
aNode = this . getDoc ( ) . createElementNS ( this . getNode ( ) . namespaceURI ,
nodeName ) ;
} catch ( ex ) {
aNode = this . getDoc ( ) . createElement ( nodeName )
}
this . getNode ( ) . appendChild ( aNode ) ;
aNode . appendChild ( tNode ) ;
}
return aNode ;
} ;
/ * *
* Builds a node using { @ link
* http : //wiki.script.aculo.us/scriptaculous/show/Builder
* script . aculo . us ' Dom Builder } notation .
* This code is taken from { @ link
* http : //wiki.script.aculo.us/scriptaculous/show/Builder
* script . aculo . us ' Dom Builder } and has been modified to suit our
* needs . < br / >
* The original parts of the code do have the following copyright
* and license notice : < br / >
* Copyright ( c ) 2005 , 2006 Thomas Fuchs ( http : //script.aculo.us,
* http : //mir.acu lo.us) <br/>
* script . aculo . us is freely distributable under the terms of an
* MIT - style licen se . // For details, see the script.aculo.us web
* site : http : //script.aculo.us/<br>
* @ author Thomas Fuchs
* @ author Stefan Strigler
* @ return The newly created node
* @ type { @ link http : //www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
* /
JSJaCPacket . prototype . buildNode = function ( elementName ) {
return JSJaCBuilder . buildNode ( this . getDoc ( ) ,
elementName ,
arguments [ 1 ] ,
arguments [ 2 ] ) ;
} ;
/ * *
* Appends node created by buildNode to this packets parent node .
* @ 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
* @ param { String } element A name plus an object hash with attributes ( optional ) plus an array of childnodes ( optional )
* @ see # buildNode
* @ return This packet
* @ type JSJaCPacket
* /
JSJaCPacket . prototype . appendNode = function ( element ) {
if ( typeof element == 'object' ) { // seems to be a prebuilt node
return this . getNode ( ) . appendChild ( element )
} else { // build node
return this . getNode ( ) . appendChild ( this . buildNode ( element ,
arguments [ 1 ] ,
arguments [ 2 ] ,
null ,
this . getNode ( ) . namespaceURI ) ) ;
}
} ;
/ * *
* A jabber / XMPP presence packet
* @ class Models the XMPP notion of a 'presence' packet
* @ extends JSJaCPacket
* /
function JSJaCPresence ( ) {
/ * *
* @ ignore
* /
this . base = JSJaCPacket ;
this . base ( 'presence' ) ;
}
JSJaCPresence . prototype = new JSJaCPacket ;
/ * *
* Sets the status message for current status . Usually this is set
* to some human readable string indicating what the user is
* doing / feel like currently .
* @ param { String } status A status message
* @ return this
* @ type JSJaCPacket
* /
JSJaCPresence . prototype . setStatus = function ( status ) {
this . _setChildNode ( "status" , status ) ;
return this ;
} ;
/ * *
* Sets the online status for this presence packet .
* @ param { String } show An XMPP complient status indicator . Must
* be one of 'chat' , 'away' , 'xa' , 'dnd'
* @ return this
* @ type JSJaCPacket
* /
JSJaCPresence . prototype . setShow = function ( show ) {
if ( show == 'chat' || show == 'away' || show == 'xa' || show == 'dnd' )
this . _setChildNode ( "show" , show ) ;
return this ;
} ;
/ * *
* Sets the priority of the resource bind to with this connection
* @ param { int } prio The priority to set this resource to
* @ return this
* @ type JSJaCPacket
* /
JSJaCPresence . prototype . setPriority = function ( prio ) {
this . _setChildNode ( "priority" , prio ) ;
return this ;
} ;
/ * *
* Some combined method that allowes for setting show , status and
* priority at once
* @ param { String } show A status message
* @ param { String } status A status indicator as defined by XMPP
* @ param { int } prio A priority for this resource
* @ return this
* @ type JSJaCPacket
* /
JSJaCPresence . prototype . setPresence = function ( show , status , prio ) {
if ( show )
this . setShow ( show ) ;
if ( status )
this . setStatus ( status ) ;
if ( prio )
this . setPriority ( prio ) ;
return this ;
} ;
/ * *
* Gets the status message of this presence
* @ return The ( human readable ) status message
* @ type String
* /
JSJaCPresence . prototype . getStatus = function ( ) {
return this . getChildVal ( 'status' ) ;
} ;
/ * *
* Gets the status of this presence .
* Either one of 'chat' , 'away' , 'xa' or 'dnd' or null .
* @ return The status indicator as defined by XMPP
* @ type String
* /
JSJaCPresence . prototype . getShow = function ( ) {
return this . getChildVal ( 'show' ) ;
} ;
/ * *
* Gets the priority of this status message
* @ return A resource priority
* @ type int
* /
JSJaCPresence . prototype . getPriority = function ( ) {
return this . getChildVal ( 'priority' ) ;
} ;
/ * *
* A jabber / XMPP iq packet
* @ class Models the XMPP notion of an 'iq' packet
* @ extends JSJaCPacket
* /
function JSJaCIQ ( ) {
/ * *
* @ ignore
* /
this . base = JSJaCPacket ;
this . base ( 'iq' ) ;
}
JSJaCIQ . prototype = new JSJaCPacket ;
/ * *
* Some combined method to set 'to' , 'type' and 'id' at once
* @ param { String } to the recepients JID
* @ param { String } type A XMPP compliant iq type ( one of 'set' , 'get' , 'result' and 'error'
* @ param { String } id A packet ID
* @ return this
* @ type JSJaCIQ
* /
JSJaCIQ . prototype . setIQ = function ( to , type , id ) {
if ( to )
this . setTo ( to ) ;
if ( type )
this . setType ( type ) ;
if ( id )
this . setID ( id ) ;
return this ;
} ;
/ * *
* Creates a 'query' child node with given XMLNS
* @ param { String } xmlns The namespace for the 'query' node
* @ return The query node
* @ type { @ link http : //www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
* /
JSJaCIQ . prototype . setQuery = function ( xmlns ) {
var query ;
try {
query = this . getDoc ( ) . createElementNS ( xmlns , 'query' ) ;
} catch ( e ) {
query = this . getDoc ( ) . createElement ( 'query' ) ;
2014-04-08 20:14:28 +02:00
query . setAttribute ( 'xmlns' , xmlns ) ;
2014-03-12 14:52:47 +01:00
}
this . getNode ( ) . appendChild ( query ) ;
return query ;
} ;
/ * *
* Gets the 'query' node of this packet
* @ return The query node
* @ type { @ link http : //www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
* /
JSJaCIQ . prototype . getQuery = function ( ) {
return this . getNode ( ) . getElementsByTagName ( 'query' ) . item ( 0 ) ;
} ;
/ * *
* Gets the XMLNS of the query node contained within this packet
* @ return The namespace of the query node
* @ type String
* /
JSJaCIQ . prototype . getQueryXMLNS = function ( ) {
if ( this . getQuery ( ) ) {
return this . getQuery ( ) . namespaceURI || this . getQuery ( ) . getAttribute ( 'xmlns' ) ;
} else {
return null ;
}
} ;
/ * *
* 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 ) .
* @ param { Element } payload A payload to be appended [ optional ]
* @ return An IQ reply packet
* @ type JSJaCIQ
* /
JSJaCIQ . prototype . reply = function ( payload ) {
var rIQ = this . clone ( ) ;
rIQ . setTo ( this . getFrom ( ) ) ;
rIQ . setFrom ( ) ;
rIQ . setType ( 'result' ) ;
if ( payload ) {
if ( typeof payload == 'string' )
rIQ . getChild ( ) . appendChild ( rIQ . getDoc ( ) . loadXML ( payload ) ) ;
else if ( payload . constructor == Array ) {
var node = rIQ . getChild ( ) ;
for ( var i = 0 ; i < payload . length ; i ++ )
if ( typeof payload [ i ] == 'string' )
node . appendChild ( rIQ . getDoc ( ) . loadXML ( payload [ i ] ) ) ;
else if ( typeof payload [ i ] == 'object' )
node . appendChild ( payload [ i ] ) ;
}
else if ( typeof payload == 'object' )
rIQ . getChild ( ) . appendChild ( payload ) ;
}
return rIQ ;
} ;
/ * *
* A jabber / XMPP message packet
* @ class Models the XMPP notion of an 'message' packet
* @ extends JSJaCPacket
* /
function JSJaCMessage ( ) {
/ * *
* @ ignore
* /
this . base = JSJaCPacket ;
this . base ( 'message' ) ;
}
JSJaCMessage . prototype = new JSJaCPacket ;
/ * *
* Sets the body of the message
* @ param { String } body Your message to be sent along
* @ return this message
* @ type JSJaCMessage
* /
JSJaCMessage . prototype . setBody = function ( body ) {
this . _setChildNode ( "body" , body ) ;
return this ;
} ;
/ * *
* Sets the subject of the message
* @ param { String } subject Your subject to be sent along
* @ return this message
* @ type JSJaCMessage
* /
JSJaCMessage . prototype . setSubject = function ( subject ) {
this . _setChildNode ( "subject" , subject ) ;
return this ;
} ;
/ * *
* Sets the 'tread' attribute for this message . This is used to identify
* threads in chat conversations
* @ param { String } thread Usually a somewhat random hash .
* @ return this message
* @ type JSJaCMessage
* /
JSJaCMessage . prototype . setThread = function ( thread ) {
this . _setChildNode ( "thread" , thread ) ;
return this ;
} ;
/ * *
* Sets the 'nick' attribute for this message .
* This is sometime sused to detect the sender nickname when he ' s not in the roster
* @ param { String } nickname
* @ return this message
* @ type JSJaCMessage
* /
JSJaCMessage . prototype . setNick = function ( nick ) {
var aNode = this . getChild ( "nick" ) ;
var tNode = this . getDoc ( ) . createTextNode ( nick ) ;
if ( aNode )
try {
aNode . replaceChild ( tNode , aNode . firstChild ) ;
} catch ( e ) { }
else {
try {
aNode = this . getDoc ( ) . createElementNS ( 'http://jabber.org/protocol/nick' ,
"nick" ) ;
} catch ( ex ) {
aNode = this . getDoc ( ) . createElement ( "nick" )
}
this . getNode ( ) . appendChild ( aNode ) ;
aNode . appendChild ( tNode ) ;
}
return this ;
} ;
/ * *
* Gets the 'thread' identifier for this message
* @ return A thread identifier
* @ type String
* /
JSJaCMessage . prototype . getThread = function ( ) {
return this . getChildVal ( 'thread' ) ;
} ;
/ * *
* Gets the body of this message
* @ return The body of this message
* @ type String
* /
JSJaCMessage . prototype . getBody = function ( ) {
return this . getChildVal ( 'body' ) ;
} ;
/ * *
* Gets the subject of this message
* @ return The subject of this message
* @ type String
* /
JSJaCMessage . prototype . getSubject = function ( ) {
return this . getChildVal ( 'subject' )
} ;
/ * *
* Gets the nickname of this message
* @ return The nickname of this message
* @ type String
* /
JSJaCMessage . prototype . getNick = function ( ) {
return this . getChildVal ( 'nick' ) ;
} ;
/ * *
* Tries to transform a w3c DOM node to JSJaC ' s internal representation
* ( JSJaCPacket type , one of JSJaCPresence , JSJaCMessage , JSJaCIQ )
* @ param : { Node
* http : //www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247}
* node The node to be transformed
* @ return A JSJaCPacket representing the given node . If node ' s root
* elemenent is not one of 'message' , 'presence' or 'iq' ,
* < code > null < / c o d e > i s b e i n g r e t u r n e d .
* @ type JSJaCPacket
* /
JSJaCPacket . wrapNode = function ( node ) {
var oPacket = null ;
switch ( node . nodeName . toLowerCase ( ) ) {
case 'presence' :
oPacket = new JSJaCPresence ( ) ;
break ;
case 'message' :
oPacket = new JSJaCMessage ( ) ;
break ;
case 'iq' :
oPacket = new JSJaCIQ ( ) ;
break ;
}
if ( oPacket ) {
oPacket . getDoc ( ) . replaceChild ( oPacket . _importNode ( node , true ) ,
oPacket . getNode ( ) ) ;
}
return oPacket ;
} ;
/ * *
* an error packet for internal use
* @ private
* @ constructor
* /
function JSJaCError ( code , type , condition ) {
var xmldoc = XmlDocument . create ( "error" , "jsjac" ) ;
xmldoc . documentElement . setAttribute ( 'code' , code ) ;
xmldoc . documentElement . setAttribute ( 'type' , type ) ;
if ( condition )
xmldoc . documentElement . appendChild ( xmldoc . createElement ( condition ) ) .
setAttribute ( 'xmlns' , 'urn:ietf:params:xml:ns:xmpp-stanzas' ) ;
return xmldoc . documentElement ;
}
/ * *
* Creates a new set of hash keys
* @ class Reflects a set of sha1 / md5 hash keys for securing sessions
* @ constructor
* @ param { Function } func The hash function to be used for creating the keys
* @ param { Debugger } oDbg Reference to debugger implementation [ optional ]
2014-11-25 23:42:38 +01:00
* /
2014-03-12 14:52:47 +01:00
function JSJaCKeys ( func , oDbg ) {
var seed = Math . random ( ) ;
/ * *
* @ private
* /
this . _k = new Array ( ) ;
this . _k [ 0 ] = seed . toString ( ) ;
if ( oDbg )
/ * *
* Reference to Debugger
* @ type Debugger
* /
this . oDbg = oDbg ;
else {
this . oDbg = { } ;
this . oDbg . log = function ( ) { } ;
}
if ( func ) {
for ( var i = 1 ; i < JSJAC _NKEYS ; i ++ ) {
this . _k [ i ] = func ( this . _k [ i - 1 ] ) ;
oDbg . log ( i + ": " + this . _k [ i ] , 4 ) ;
}
}
/ * *
* @ private
* /
this . _indexAt = JSJAC _NKEYS - 1 ;
/ * *
* Gets next key from stack
* @ return New hash key
* @ type String
* /
this . getKey = function ( ) {
return this . _k [ this . _indexAt -- ] ;
} ;
/ * *
* Indicates whether there ' s only one key left
* @ return < code > true < / c o d e > i f t h e r e ' s o n l y o n e k e y l e f t , f a l s e o t h e r w i s e
* @ type boolean
* /
this . lastKey = function ( ) { return ( this . _indexAt == 0 ) ; } ;
/ * *
* Returns number of overall / initial stack size
* @ return Number of keys created
* @ type int
* /
this . size = function ( ) { return this . _k . length ; } ;
/ * *
* @ private
* /
this . _getSuspendVars = function ( ) {
return ( '_k,_indexAt' ) . split ( ',' ) ;
}
}
/ * *
* @ fileoverview Contains all things in common for all subtypes of connections
* supported .
* @ author Stefan Strigler steve @ zeank . in - berlin . de
2014-11-25 20:12:58 +01:00
* @ version 1.3
2014-03-12 14:52:47 +01:00
* /
/ * *
* Creates a new Jabber connection ( a connection to a jabber server )
* @ class Somewhat abstract base class for jabber connections . Contains all
* of the code in common for all jabber connections
* @ constructor
* @ param { JSON http : //www.json.org/index} oArg JSON with properties: <br>
* * < code > httpbase < / c o d e > t h e h t t p b a s e a d d r e s s o f t h e s e r v i c e t o b e u s e d f o r
* connecting to jabber < br >
* * < code > oDbg < / c o d e > ( o p t i o n a l ) a r e f e r e n c e t o a d e b u g g e r i n t e r f a c e
* /
function JSJaCConnection ( oArg ) {
if ( oArg && oArg . oDbg && oArg . oDbg . log ) {
/ * *
* Reference to debugger interface
* ( needs to implement method < code > log < / c o d e > )
* @ type Debugger
* /
this . oDbg = oArg . oDbg ;
} else {
this . oDbg = new Object ( ) ; // always initialise a debugger
this . oDbg . log = function ( ) { } ;
}
if ( oArg && oArg . timerval )
this . setPollInterval ( oArg . timerval ) ;
else
this . setPollInterval ( JSJAC _TIMERVAL ) ;
if ( oArg && oArg . httpbase )
/ * *
* @ private
* /
this . _httpbase = oArg . httpbase ;
if ( oArg && oArg . allow _plain )
/ * *
* @ private
* /
this . allow _plain = oArg . allow _plain ;
else
this . allow _plain = JSJAC _ALLOW _PLAIN ;
if ( oArg && oArg . cookie _prefix )
/ * *
* @ private
* /
this . _cookie _prefix = oArg . cookie _prefix ;
else
this . _cookie _prefix = "" ;
/ * *
* @ private
* /
this . _connected = false ;
/ * *
* @ private
* /
this . _events = new Array ( ) ;
/ * *
* @ private
* /
this . _keys = null ;
/ * *
* @ private
* /
this . _ID = 0 ;
/ * *
* @ private
* /
this . _inQ = new Array ( ) ;
/ * *
* @ private
* /
this . _pQueue = new Array ( ) ;
/ * *
* @ private
* /
this . _regIDs = new Array ( ) ;
/ * *
* @ private
* /
this . _req = new Array ( ) ;
/ * *
* @ private
* /
this . _status = 'intialized' ;
/ * *
* @ private
* /
this . _errcnt = 0 ;
/ * *
* @ private
* /
this . _inactivity = JSJAC _INACTIVITY ;
/ * *
* @ private
* /
this . _sendRawCallbacks = new Array ( ) ;
}
// Generates an ID
var STANZA _ID = 1 ;
function genID ( ) {
return STANZA _ID ++ ;
}
JSJaCConnection . prototype . connect = function ( oArg ) {
this . _setStatus ( 'connecting' ) ;
this . domain = oArg . domain || 'localhost' ;
this . username = oArg . username ;
this . resource = oArg . resource ;
this . pass = oArg . pass ;
this . register = oArg . register ;
this . authhost = oArg . authhost || this . domain ;
this . authtype = oArg . authtype || 'sasl' ;
if ( oArg . xmllang && oArg . xmllang != '' )
this . _xmllang = oArg . xmllang ;
else
this . _xmllang = 'en' ;
this . host = oArg . host || this . domain ;
this . port = oArg . port || 5222 ;
if ( oArg . secure )
this . secure = 'true' ;
else
this . secure = 'false' ;
if ( oArg . wait )
this . _wait = oArg . wait ;
this . jid = this . username + '@' + this . domain ;
this . fulljid = this . jid + '/' + this . resource ;
this . _rid = Math . round ( 100000.5 + ( ( ( 900000.49999 ) - ( 100000.5 ) ) * Math . random ( ) ) ) ;
// setupRequest must be done after rid is created but before first use in reqstr
var slot = this . _getFreeSlot ( ) ;
this . _req [ slot ] = this . _setupRequest ( true ) ;
var reqstr = this . _getInitialRequestString ( ) ;
this . oDbg . log ( reqstr , 4 ) ;
this . _req [ slot ] . r . onreadystatechange =
JSJaC . bind ( function ( ) {
var r = this . _req [ slot ] . r ;
if ( r . readyState == 4 ) {
this . oDbg . log ( "async recv: " + r . responseText , 4 ) ;
this . _handleInitialResponse ( r ) ; // handle response
}
} , this ) ;
if ( typeof ( this . _req [ slot ] . r . onerror ) != 'undefined' ) {
this . _req [ slot ] . r . onerror =
JSJaC . bind ( function ( e ) {
this . oDbg . log ( 'XmlHttpRequest error' , 1 ) ;
return false ;
} , this ) ;
}
this . _req [ slot ] . r . send ( reqstr ) ;
} ;
/ * *
* Tells whether this connection is connected
* @ return < code > true < / c o d e > i f t h i s c o n n e c t i o n s i s c o n n e c t e d ,
* < code > false < / c o d e > o t h e r w i s e
* @ type boolean
* /
JSJaCConnection . prototype . connected = function ( ) { return this . _connected ; } ;
/ * *
* Disconnects from jabber server and terminates session ( if applicable )
* /
JSJaCConnection . prototype . disconnect = function ( ) {
this . _setStatus ( 'disconnecting' ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
if ( ! this . connected ( ) )
return ;
this . _connected = false ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
clearInterval ( this . _interval ) ;
clearInterval ( this . _inQto ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
if ( this . _timeout )
clearTimeout ( this . _timeout ) ; // remove timer
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
var slot = this . _getFreeSlot ( ) ;
// Intentionally synchronous
this . _req [ slot ] = this . _setupRequest ( false ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
request = this . _getRequestString ( false , true ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
this . oDbg . log ( "Disconnecting: " + request , 4 ) ;
this . _req [ slot ] . r . send ( request ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
try {
DataStore . removeDB ( MINI _HASH , 'jsjac' , 'state' ) ;
} catch ( e ) { }
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
this . oDbg . log ( "Disconnected: " + this . _req [ slot ] . r . responseText , 2 ) ;
this . _handleEvent ( 'ondisconnect' ) ;
} ;
/ * *
* Gets current value of polling interval
* @ return Polling interval in milliseconds
* @ type int
* /
JSJaCConnection . prototype . getPollInterval = function ( ) {
return this . _timerval ;
} ;
/ * *
* Registers an event handler ( callback ) for this connection .
* < p > Note : All of the packet handlers for specific packets ( like
* message _in , presence _in and iq _in ) fire only if there ' s no
* callback associated with the id . < br >
* < p > Example : < br / >
* < code > con . registerHandler ( 'iq' , 'query' , 'jabber:iq:version' , handleIqVersion ) ; < / c o d e >
* @ param { String } event One of
* < ul >
* < li > onConnect - connection has been established and authenticated < / l i >
* < li > onDisconnect - connection has been disconnected < / l i >
* < li > onResume - connection has been resumed < / l i >
* < li > onStatusChanged - connection status has changed , current
* status as being passed argument to handler . See { @ link # status } . < / l i >
* < li > onError - an error has occured , error node is supplied as
* argument , like this : < br > < code > & lt ; error code = '404' type = 'cancel' & gt ; < br >
* & lt ; item - not - found xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' / & gt ; < br >
* & lt ; / e r r o r & g t ; < / c o d e > < / l i >
* < li > packet _in - a packet has been received ( argument : the
* packet ) < / l i >
* < li > packet _out - a packet is to be sent ( argument : the
* packet ) < / l i >
* < li > message _in | message - a message has been received ( argument :
* the packet ) < / l i >
* < li > message _out - a message packet is to be sent ( argument : the
* packet ) < / l i >
* < li > presence _in | presence - a presence has been received
* ( argument : the packet ) < / l i >
* < li > presence _out - a presence packet is to be sent ( argument : the
* packet ) < / l i >
* < li > iq _in | iq - an iq has been received ( argument : the packet ) < / l i >
* < li > iq _out - an iq is to be sent ( argument : the packet ) < / l i >
* < / u l >
* @ param { String } childName A childnode ' s name that must occur within a
* retrieved packet [ optional ]
* @ param { String } childNS A childnode ' s namespace that must occure within
* a retrieved packet ( works only if childName is given ) [ optional ]
* @ 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 ]
* @ 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 .
* /
JSJaCConnection . prototype . registerHandler = function ( event ) {
event = event . toLowerCase ( ) ; // don't be case-sensitive here
var eArg = { handler : arguments [ arguments . length - 1 ] ,
childName : '*' ,
childNS : '*' ,
type : '*' } ;
if ( arguments . length > 2 )
eArg . childName = arguments [ 1 ] ;
if ( arguments . length > 3 )
eArg . childNS = arguments [ 2 ] ;
if ( arguments . length > 4 )
eArg . type = arguments [ 3 ] ;
if ( ! this . _events [ event ] )
this . _events [ event ] = new Array ( eArg ) ;
else
this . _events [ event ] = this . _events [ event ] . concat ( eArg ) ;
// sort events in order how specific they match criterias thus using
// wildcard patterns puts them back in queue when it comes to
// bubbling the event
this . _events [ event ] =
this . _events [ event ] . sort ( function ( a , b ) {
var aRank = 0 ;
var bRank = 0 ;
with ( a ) {
if ( type == '*' )
aRank ++ ;
if ( childNS == '*' )
aRank ++ ;
if ( childName == '*' )
aRank ++ ;
}
with ( b ) {
if ( type == '*' )
bRank ++ ;
if ( childNS == '*' )
bRank ++ ;
if ( childName == '*' )
bRank ++ ;
}
if ( aRank > bRank )
return 1 ;
if ( aRank < bRank )
return - 1 ;
return 0 ;
} ) ;
this . oDbg . log ( "registered handler for event '" + event + "'" , 2 ) ;
} ;
JSJaCConnection . prototype . unregisterHandler = function ( event , handler ) {
event = event . toLowerCase ( ) ; // don't be case-sensitive here
if ( ! this . _events [ event ] )
return ;
var arr = this . _events [ event ] , res = new Array ( ) ;
for ( var i = 0 ; i < arr . length ; i ++ )
if ( arr [ i ] . handler != handler )
res . push ( arr [ i ] ) ;
if ( arr . length != res . length ) {
this . _events [ event ] = res ;
this . oDbg . log ( "unregistered handler for event '" + event + "'" , 2 ) ;
}
} ;
/ * *
* Register for iq packets of type 'get' .
* @ param { String } childName A childnode ' s name that must occur within a
* retrieved packet
* @ param { String } childNS A childnode ' s namespace that must occure within
* a retrieved packet ( works only if childName is given )
* @ 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 .
* /
JSJaCConnection . prototype . registerIQGet = function ( childName , childNS , handler ) {
this . registerHandler ( 'iq' , childName , childNS , 'get' , handler ) ;
} ;
/ * *
* Register for iq packets of type 'set' .
* @ param { String } childName A childnode ' s name that must occur within a
* retrieved packet
* @ param { String } childNS A childnode ' s namespace that must occure within
* a retrieved packet ( works only if childName is given )
* @ 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 .
* /
JSJaCConnection . prototype . registerIQSet = function ( childName , childNS , handler ) {
this . registerHandler ( 'iq' , childName , childNS , 'set' , handler ) ;
} ;
/ * *
* Resumes this connection from saved state ( cookie )
* @ return Whether resume was successful
* @ type boolean
* /
JSJaCConnection . prototype . resume = function ( ) {
try {
var json = DataStore . getDB ( MINI _HASH , 'jsjac' , 'state' ) ;
this . oDbg . log ( 'read cookie: ' + json , 2 ) ;
DataStore . removeDB ( MINI _HASH , 'jsjac' , 'state' ) ;
return this . resumeFromData ( JSJaCJSON . parse ( json ) ) ;
} catch ( e ) { }
return false ; // sth went wrong
} ;
/ * *
* Resumes BOSH connection from data
* @ param { Object } serialized jsjac state information
* @ return Whether resume was successful
* @ type boolean
* /
JSJaCConnection . prototype . resumeFromData = function ( data ) {
try {
this . _setStatus ( 'resuming' ) ;
for ( var i in data )
if ( data . hasOwnProperty ( i ) )
this [ i ] = data [ i ] ;
// copy keys - not being very generic here :-/
if ( this . _keys ) {
this . _keys2 = new JSJaCKeys ( ) ;
var u = this . _keys2 . _getSuspendVars ( ) ;
for ( var i = 0 ; i < u . length ; i ++ )
this . _keys2 [ u [ i ] ] = this . _keys [ u [ i ] ] ;
this . _keys = this . _keys2 ;
}
if ( this . _connected ) {
// don't poll too fast!
this . _handleEvent ( 'onresume' ) ;
setTimeout ( JSJaC . bind ( this . _resume , this ) , this . getPollInterval ( ) ) ;
this . _interval = setInterval ( JSJaC . bind ( this . _checkQueue , this ) ,
2014-04-08 20:14:28 +02:00
JSJAC _CHECKQUEUEINTERVAL ) ;
2014-03-12 14:52:47 +01:00
this . _inQto = setInterval ( JSJaC . bind ( this . _checkInQ , this ) ,
2014-04-08 20:14:28 +02:00
JSJAC _CHECKINQUEUEINTERVAL ) ;
2014-03-12 14:52:47 +01:00
}
return ( this . _connected === true ) ;
} catch ( e ) {
if ( e . message )
this . oDbg . log ( "Resume failed: " + e . message , 1 ) ;
else
this . oDbg . log ( "Resume failed: " + e , 1 ) ;
return false ;
}
} ;
/ * *
* Sends a JSJaCPacket
* @ param { JSJaCPacket } packet The packet to send
* @ param { Function } cb The callback to be called if there ' s a reply
* to this packet ( identified by id ) [ optional ]
* @ param { Object } arg Arguments passed to the callback
* ( additionally to the packet received ) [ optional ]
* @ return 'true' if sending was successfull , 'false' otherwise
* @ type boolean
* /
JSJaCConnection . prototype . send = function ( packet , cb , arg ) {
if ( ! packet || ! packet . pType ) {
this . oDbg . log ( "no packet: " + packet , 1 ) ;
return false ;
}
if ( ! this . connected ( ) )
return false ;
// generate an ID for the packet
if ( ! packet . getID ( ) )
packet . setID ( genID ( ) ) ;
// packet xml:lang
if ( ! packet . getXMLLang ( ) )
packet . setXMLLang ( XML _LANG ) ;
// remember id for response if callback present
if ( cb )
2014-04-08 20:14:28 +02:00
this . _registerPID ( packet , cb , arg ) ;
2014-03-12 14:52:47 +01:00
try {
this . _handleEvent ( packet . pType ( ) + '_out' , packet ) ;
this . _handleEvent ( "packet_out" , packet ) ;
this . _pQueue = this . _pQueue . concat ( packet . xml ( ) ) ;
} catch ( e ) {
this . oDbg . log ( e . toString ( ) , 1 ) ;
return false ;
}
return true ;
} ;
/ * *
* Sends an IQ packet . Has default handlers for each reply type .
* Those maybe overriden by passing an appropriate handler .
* @ param { JSJaCIQPacket } iq - the iq packet to send
* @ param { Object } handlers - object with properties 'error_handler' ,
* 'result_handler' and 'default_handler'
* with appropriate functions
* @ param { Object } arg - argument to handlers
* @ return 'true' if sending was successfull , 'false' otherwise
* @ type boolean
* /
JSJaCConnection . prototype . sendIQ = function ( iq , handlers , arg ) {
if ( ! iq || iq . pType ( ) != 'iq' ) {
return false ;
}
handlers = handlers || { } ;
var error _handler = handlers . error _handler || JSJaC . bind ( function ( aIq ) {
this . oDbg . log ( aIq . xml ( ) , 1 ) ;
} , this ) ;
var result _handler = handlers . result _handler || JSJaC . bind ( function ( aIq ) {
this . oDbg . log ( aIq . xml ( ) , 2 ) ;
} , this ) ;
var iqHandler = function ( aIq , arg ) {
switch ( aIq . getType ( ) ) {
case 'error' :
error _handler ( aIq ) ;
break ;
case 'result' :
result _handler ( aIq , arg ) ;
break ;
}
} ;
return this . send ( iq , iqHandler , arg ) ;
} ;
/ * *
* Sets polling interval for this connection
2014-04-08 20:14:28 +02:00
* @ param { int } timerval Milliseconds to set timer to
2014-03-12 14:52:47 +01:00
* @ return effective interval this connection has been set to
* @ type int
* /
JSJaCConnection . prototype . setPollInterval = function ( timerval ) {
if ( timerval && ! isNaN ( timerval ) )
this . _timerval = timerval ;
return this . _timerval ;
} ;
/ * *
* Returns current status of this connection
* @ return String to denote current state . One of
* < ul >
* < li > 'initializing' ... well
* < li > 'connecting' if connect ( ) was called
* < li > 'resuming' if resume ( ) was called
* < li > 'processing' if it ' s about to operate as normal
* < li > 'onerror_fallback' if there was an error with the request object
* < li > 'protoerror_fallback' if there was an error at the http binding protocol flow ( most likely that ' s where you interested in )
* < li > 'internal_server_error' in case of an internal server error
* < li > 'suspending' if suspend ( ) is being called
* < li > 'aborted' if abort ( ) was called
* < li > 'disconnecting' if disconnect ( ) has been called
* < / u l >
* @ type String
* /
JSJaCConnection . prototype . status = function ( ) { return this . _status ; } ;
/ * *
* Suspends this connection ( saving state for later resume )
* Saves state to cookie
* @ return Whether suspend ( saving to cookie ) was successful
* @ type boolean
* /
JSJaCConnection . prototype . suspend = function ( has _pause ) {
var data = this . suspendToData ( has _pause ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
try {
var c = DataStore . setDB ( MINI _HASH , 'jsjac' , 'state' , JSJaCJSON . toString ( data ) ) ;
return c ;
} catch ( e ) {
this . oDbg . log ( "Failed creating cookie '" + this . _cookie _prefix +
"JSJaC_State': " + e . message , 1 ) ;
}
return false ;
} ;
/ * *
* Suspend connection and return serialized JSJaC connection state
* @ return JSJaC connection state object
* @ type Object
* /
JSJaCConnection . prototype . suspendToData = function ( has _pause ) {
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
// remove timers
if ( has _pause ) {
clearTimeout ( this . _timeout ) ;
clearInterval ( this . _interval ) ;
clearInterval ( this . _inQto ) ;
this . _suspend ( ) ;
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
var u = ( '_connected,_keys,_ID,_inQ,_pQueue,_regIDs,_errcnt,_inactivity,domain,username,resource,jid,fulljid,_sid,_httpbase,_timerval,_is_polling' ) . split ( ',' ) ;
u = u . concat ( this . _getSuspendVars ( ) ) ;
var s = new Object ( ) ;
for ( var i = 0 ; i < u . length ; i ++ ) {
if ( ! this [ u [ i ] ] ) continue ; // hu? skip these!
if ( this [ u [ i ] ] . _getSuspendVars ) {
var uo = this [ u [ i ] ] . _getSuspendVars ( ) ;
var o = new Object ( ) ;
for ( var j = 0 ; j < uo . length ; j ++ )
o [ uo [ j ] ] = this [ u [ i ] ] [ uo [ j ] ] ;
} else
var o = this [ u [ i ] ] ;
s [ u [ i ] ] = o ;
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
if ( has _pause ) {
this . _connected = false ;
this . _setStatus ( 'suspending' ) ;
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
return s ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _abort = function ( ) {
clearTimeout ( this . _timeout ) ; // remove timer
clearInterval ( this . _inQto ) ;
clearInterval ( this . _interval ) ;
this . _connected = false ;
this . _setStatus ( 'aborted' ) ;
this . oDbg . log ( "Disconnected." , 1 ) ;
this . _handleEvent ( 'ondisconnect' ) ;
this . _handleEvent ( 'onerror' ,
JSJaCError ( '500' , 'cancel' , 'service-unavailable' ) ) ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _checkInQ = function ( ) {
for ( var i = 0 ; i < this . _inQ . length && i < 10 ; i ++ ) {
var item = this . _inQ [ 0 ] ;
this . _inQ = this . _inQ . slice ( 1 , this . _inQ . length ) ;
var packet = JSJaCPacket . wrapNode ( item ) ;
if ( ! packet )
return ;
this . _handleEvent ( "packet_in" , packet ) ;
if ( packet . pType && ! this . _handlePID ( packet ) ) {
this . _handleEvent ( packet . pType ( ) + '_in' , packet ) ;
this . _handleEvent ( packet . pType ( ) , packet ) ;
}
}
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _checkQueue = function ( ) {
if ( this . _pQueue . length != 0 )
this . _process ( ) ;
return true ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _doAuth = function ( ) {
if ( this . has _sasl && this . authtype == 'nonsasl' )
this . oDbg . log ( "Warning: SASL present but not used" , 1 ) ;
if ( ! this . _doSASLAuth ( ) &&
! this . _doLegacyAuth ( ) ) {
this . oDbg . log ( "Auth failed for authtype " + this . authtype , 1 ) ;
this . disconnect ( ) ;
return false ;
}
return true ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _doInBandReg = function ( ) {
if ( this . authtype == 'saslanon' || this . authtype == 'anonymous' )
return ; // bullshit - no need to register if anonymous
/ * * * *
* In - Band Registration see JEP - 0077
* /
var iq = new JSJaCIQ ( ) ;
iq . setType ( 'set' ) ;
iq . setID ( 'reg1' ) ;
iq . appendNode ( "query" , { xmlns : "jabber:iq:register" } ,
[ [ "username" , this . username ] ,
[ "password" , this . pass ] ] ) ;
this . send ( iq , this . _doInBandRegDone ) ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _doInBandRegDone = function ( iq ) {
if ( iq && iq . getType ( ) == 'error' ) { // we failed to register
this . oDbg . log ( "registration failed for " + this . username , 0 ) ;
this . _handleEvent ( 'onerror' , iq . getChild ( 'error' ) ) ;
return ;
}
this . oDbg . log ( this . username + " registered succesfully" , 0 ) ;
this . _doAuth ( ) ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _doLegacyAuth = function ( ) {
if ( this . authtype != 'nonsasl' && this . authtype != 'anonymous' )
return false ;
/ * * * *
* Non - SASL Authentication as described in JEP - 0078
* /
var iq = new JSJaCIQ ( ) ;
iq . setIQ ( null , 'get' , 'auth1' ) ;
iq . appendNode ( 'query' , { xmlns : 'jabber:iq:auth' } ,
[ [ 'username' , this . username ] ] ) ;
this . send ( iq , this . _doLegacyAuth2 ) ;
return true ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _doLegacyAuth2 = function ( iq ) {
if ( ! iq || iq . getType ( ) != 'result' ) {
if ( iq && iq . getType ( ) == 'error' )
this . _handleEvent ( 'onerror' , iq . getChild ( 'error' ) ) ;
this . disconnect ( ) ;
return ;
}
var use _digest = ( iq . getChild ( 'digest' ) != null ) ;
/ * * * *
* Send authentication
* /
var iq = new JSJaCIQ ( ) ;
iq . setIQ ( null , 'set' , 'auth2' ) ;
query = iq . appendNode ( 'query' , { xmlns : 'jabber:iq:auth' } ,
[ [ 'username' , this . username ] ,
[ 'resource' , this . resource ] ] ) ;
if ( use _digest ) { // digest login
query . appendChild ( iq . buildNode ( 'digest' , { xmlns : 'jabber:iq:auth' } ,
hex _sha1 ( this . streamid + this . pass ) ) ) ;
} else if ( this . allow _plain ) { // use plaintext auth
query . appendChild ( iq . buildNode ( 'password' , { xmlns : 'jabber:iq:auth' } ,
this . pass ) ) ;
} else {
this . oDbg . log ( "no valid login mechanism found" , 1 ) ;
this . disconnect ( ) ;
return false ;
}
this . send ( iq , this . _doLegacyAuthDone ) ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _doLegacyAuthDone = function ( iq ) {
if ( iq . getType ( ) != 'result' ) { // auth' failed
if ( iq . getType ( ) == 'error' )
this . _handleEvent ( 'onerror' , iq . getChild ( 'error' ) ) ;
this . disconnect ( ) ;
} else
this . _handleEvent ( 'onconnect' ) ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _doSASLAuth = function ( ) {
if ( this . authtype == 'nonsasl' || this . authtype == 'anonymous' )
return false ;
if ( this . authtype == 'saslanon' ) {
if ( this . mechs [ 'ANONYMOUS' ] ) {
this . oDbg . log ( "SASL using mechanism 'ANONYMOUS'" , 2 ) ;
return this . _sendRaw ( "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>" ,
this . _doSASLAuthDone ) ;
}
this . oDbg . log ( "SASL ANONYMOUS requested but not supported" , 1 ) ;
} else {
if ( this . mechs [ 'DIGEST-MD5' ] ) {
this . oDbg . log ( "SASL using mechanism 'DIGEST-MD5'" , 2 ) ;
return this . _sendRaw ( "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>" ,
this . _doSASLAuthDigestMd5S1 ) ;
} else if ( this . allow _plain && this . mechs [ 'PLAIN' ] ) {
this . oDbg . log ( "SASL using mechanism 'PLAIN'" , 2 ) ;
var authStr = this . username + '@' +
this . domain + String . fromCharCode ( 0 ) +
this . username + String . fromCharCode ( 0 ) +
this . pass ;
this . oDbg . log ( "authenticating with '" + authStr + "'" , 2 ) ;
authStr = b64encode ( authStr ) ;
return this . _sendRaw ( "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" + authStr + "</auth>" ,
this . _doSASLAuthDone ) ;
}
this . oDbg . log ( "No SASL mechanism applied" , 1 ) ;
this . authtype = 'nonsasl' ; // fallback
}
return false ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _doSASLAuthDigestMd5S1 = function ( el ) {
if ( el . nodeName != "challenge" ) {
this . oDbg . log ( "challenge missing" , 1 ) ;
this . _handleEvent ( 'onerror' , JSJaCError ( '401' , 'auth' , 'not-authorized' ) ) ;
this . disconnect ( ) ;
} else {
var challenge = b64decode ( el . firstChild . nodeValue ) ;
this . oDbg . log ( "got challenge: " + challenge , 2 ) ;
this . _nonce = challenge . substring ( challenge . indexOf ( "nonce=" ) + 7 ) ;
this . _nonce = this . _nonce . substring ( 0 , this . _nonce . indexOf ( "\"" ) ) ;
this . oDbg . log ( "nonce: " + this . _nonce , 2 ) ;
if ( this . _nonce == '' || this . _nonce . indexOf ( '\"' ) != - 1 ) {
this . oDbg . log ( "nonce not valid, aborting" , 1 ) ;
this . disconnect ( ) ;
return ;
}
this . _digest _uri = "xmpp/" ;
// if (typeof(this.host) != 'undefined' && this.host != '') {
// this._digest-uri += this.host;
// if (typeof(this.port) != 'undefined' && this.port)
// this._digest-uri += ":" + this.port;
// this._digest-uri += '/';
// }
this . _digest _uri += this . domain ;
this . _cnonce = cnonce ( 14 ) ;
this . _nc = '00000001' ;
var X = this . username + ':' + this . domain + ':' + this . pass ;
var Y = rstr _md5 ( str2rstr _utf8 ( X ) ) ;
var A1 = Y + ':' + this . _nonce + ':' + this . _cnonce ;
var HA1 = rstr2hex ( rstr _md5 ( A1 ) ) ;
var A2 = 'AUTHENTICATE:' + this . _digest _uri ;
var HA2 = hex _md5 ( A2 ) ;
var response = hex _md5 ( HA1 + ':' + this . _nonce + ':' + this . _nc + ':' +
this . _cnonce + ':auth:' + HA2 ) ;
var rPlain = 'username="' + this . username + '",realm="' + this . domain +
'",nonce="' + this . _nonce + '",cnonce="' + this . _cnonce + '",nc="' + this . _nc +
'",qop=auth,digest-uri="' + this . _digest _uri + '",response="' + response +
'",charset="utf-8"' ;
this . oDbg . log ( "response: " + rPlain , 2 ) ;
this . _sendRaw ( "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
b64encode ( rPlain ) + "</response>" ,
this . _doSASLAuthDigestMd5S2 ) ;
}
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _doSASLAuthDigestMd5S2 = function ( el ) {
if ( el . nodeName == 'failure' ) {
if ( el . xml )
this . oDbg . log ( "auth error: " + el . xml , 1 ) ;
else
this . oDbg . log ( "auth error" , 1 ) ;
this . _handleEvent ( 'onerror' , JSJaCError ( '401' , 'auth' , 'not-authorized' ) ) ;
this . disconnect ( ) ;
return ;
}
var response = b64decode ( el . firstChild . nodeValue ) ;
this . oDbg . log ( "response: " + response , 2 ) ;
var rspauth = response . substring ( response . indexOf ( "rspauth=" ) + 8 ) ;
this . oDbg . log ( "rspauth: " + rspauth , 2 ) ;
var X = this . username + ':' + this . domain + ':' + this . pass ;
var Y = rstr _md5 ( str2rstr _utf8 ( X ) ) ;
var A1 = Y + ':' + this . _nonce + ':' + this . _cnonce ;
var HA1 = rstr2hex ( rstr _md5 ( A1 ) ) ;
var A2 = ':' + this . _digest _uri ;
var HA2 = hex _md5 ( A2 ) ;
var rsptest = hex _md5 ( HA1 + ':' + this . _nonce + ':' + this . _nc + ':' +
this . _cnonce + ':auth:' + HA2 ) ;
this . oDbg . log ( "rsptest: " + rsptest , 2 ) ;
if ( rsptest != rspauth ) {
this . oDbg . log ( "SASL Digest-MD5: server repsonse with wrong rspauth" , 1 ) ;
this . disconnect ( ) ;
return ;
}
if ( el . nodeName == 'success' ) {
this . _reInitStream ( JSJaC . bind ( this . _doStreamBind , this ) ) ;
} else { // some extra turn
this . _sendRaw ( "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>" ,
this . _doSASLAuthDone ) ;
}
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _doSASLAuthDone = function ( el ) {
if ( el . nodeName != 'success' ) {
this . oDbg . log ( "auth failed" , 1 ) ;
this . _handleEvent ( 'onerror' , JSJaCError ( '401' , 'auth' , 'not-authorized' ) ) ;
this . disconnect ( ) ;
} else {
this . _reInitStream ( JSJaC . bind ( this . _doStreamBind , this ) ) ;
}
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _doStreamBind = function ( ) {
var iq = new JSJaCIQ ( ) ;
iq . setIQ ( null , 'set' , 'bind_1' ) ;
iq . appendNode ( "bind" , { xmlns : "urn:ietf:params:xml:ns:xmpp-bind" } ,
[ [ "resource" , this . resource ] ] ) ;
this . oDbg . log ( iq . xml ( ) ) ;
this . send ( iq , this . _doXMPPSess ) ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _doXMPPSess = function ( iq ) {
if ( iq . getType ( ) != 'result' || iq . getType ( ) == 'error' ) { // failed
this . disconnect ( ) ;
if ( iq . getType ( ) == 'error' )
this . _handleEvent ( 'onerror' , iq . getChild ( 'error' ) ) ;
return ;
}
this . fulljid = iq . getChildVal ( "jid" ) ;
this . jid = this . fulljid . substring ( 0 , this . fulljid . lastIndexOf ( '/' ) ) ;
2014-04-08 20:14:28 +02:00
if ( ! this . legacy _sessions ) {
this . _handleEvent ( 'onconnect' ) ;
return ;
}
2014-03-12 14:52:47 +01:00
iq = new JSJaCIQ ( ) ;
iq . setIQ ( null , 'set' , 'sess_1' ) ;
iq . appendNode ( "session" , { xmlns : "urn:ietf:params:xml:ns:xmpp-session" } ,
[ ] ) ;
this . oDbg . log ( iq . xml ( ) ) ;
this . send ( iq , this . _doXMPPSessDone ) ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _doXMPPSessDone = function ( iq ) {
if ( iq . getType ( ) != 'result' || iq . getType ( ) == 'error' ) { // failed
this . disconnect ( ) ;
if ( iq . getType ( ) == 'error' )
this . _handleEvent ( 'onerror' , iq . getChild ( 'error' ) ) ;
return ;
} else
this . _handleEvent ( 'onconnect' ) ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _handleEvent = function ( event , arg ) {
event = event . toLowerCase ( ) ; // don't be case-sensitive here
this . oDbg . log ( "incoming event '" + event + "'" , 3 ) ;
if ( ! this . _events [ event ] )
return ;
this . oDbg . log ( "handling event '" + event + "'" , 2 ) ;
for ( var i = 0 ; i < this . _events [ event ] . length ; i ++ ) {
var aEvent = this . _events [ event ] [ i ] ;
if ( typeof aEvent . handler == 'function' ) {
try {
if ( arg ) {
if ( arg . pType ) { // it's a packet
if ( ( ! arg . getNode ( ) . hasChildNodes ( ) && aEvent . childName != '*' ) ||
2014-04-08 20:14:28 +02:00
( arg . getNode ( ) . hasChildNodes ( ) &&
! arg . getChild ( aEvent . childName , aEvent . childNS ) ) )
2014-03-12 14:52:47 +01:00
continue ;
if ( aEvent . type != '*' &&
arg . getType ( ) != aEvent . type )
continue ;
this . oDbg . log ( aEvent . childName + "/" + aEvent . childNS + "/" + aEvent . type + " => match for handler " + aEvent . handler , 3 ) ;
}
if ( aEvent . handler ( arg ) ) {
// handled!
break ;
}
}
else
if ( aEvent . handler ( ) ) {
// handled!
break ;
}
} catch ( e ) {
if ( e . fileName && e . lineNumber ) {
this . oDbg . log ( aEvent . handler + "\n>>>" + e . name + ": " + e . message + ' in ' + e . fileName + ' line ' + e . lineNumber , 1 ) ;
} else {
this . oDbg . log ( aEvent . handler + "\n>>>" + e . name + ": " + e . message , 1 ) ;
}
}
}
}
} ;
/ * *
* @ private
* /
2014-04-08 20:14:28 +02:00
JSJaCConnection . prototype . _handlePID = function ( packet ) {
if ( ! packet . getID ( ) )
2014-03-12 14:52:47 +01:00
return false ;
2014-04-08 20:14:28 +02:00
var jid = packet . getFrom ( ) || this . jid ;
if ( packet . getFrom ( ) == this . domain )
jid = this . jid ;
var id = packet . getID ( ) ;
if ( this . _regIDs [ jid ] && this . _regIDs [ jid ] [ id ] ) {
try {
this . oDbg . log ( "handling id " + id , 3 ) ;
var reg = this . _regIDs [ jid ] [ id ] ;
if ( reg . cb . call ( this , packet , reg . arg ) === false ) {
// don't unregister
return false ;
} else {
delete this . _regIDs [ jid ] [ id ] ;
2014-03-12 14:52:47 +01:00
return true ;
}
2014-04-08 20:14:28 +02:00
} catch ( e ) {
// broken handler?
this . oDbg . log ( e . name + ": " + e . message , 1 ) ;
delete this . _regIDs [ jid ] [ id ] ;
return true ;
2014-03-12 14:52:47 +01:00
}
2014-04-08 20:14:28 +02:00
} else {
this . oDbg . log ( "not handling id '" + id + "' from jid " + jid , 1 ) ;
2014-03-12 14:52:47 +01:00
}
return false ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _handleResponse = function ( req ) {
var rootEl = this . _parseResponse ( req ) ;
if ( ! rootEl )
return ;
for ( var i = 0 ; i < rootEl . childNodes . length ; i ++ ) {
if ( this . _sendRawCallbacks . length ) {
var cb = this . _sendRawCallbacks [ 0 ] ;
this . _sendRawCallbacks = this . _sendRawCallbacks . slice ( 1 , this . _sendRawCallbacks . length ) ;
cb . fn . call ( this , rootEl . childNodes . item ( i ) , cb . arg ) ;
continue ;
}
this . _inQ = this . _inQ . concat ( rootEl . childNodes . item ( i ) ) ;
}
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _parseStreamFeatures = function ( doc ) {
if ( ! doc ) {
this . oDbg . log ( "nothing to parse ... aborting" , 1 ) ;
return false ;
}
var errorTag ;
if ( doc . getElementsByTagNameNS ) {
errorTag = doc . getElementsByTagNameNS ( "http://etherx.jabber.org/streams" , "error" ) . item ( 0 ) ;
} else {
var errors = doc . getElementsByTagName ( "error" ) ;
for ( var i = 0 ; i < errors . length ; i ++ )
if ( errors . item ( i ) . namespaceURI == "http://etherx.jabber.org/streams" ||
errors . item ( i ) . getAttribute ( 'xmlns' ) == "http://etherx.jabber.org/streams" ) {
errorTag = errors . item ( i ) ;
break ;
}
}
if ( errorTag ) {
this . _setStatus ( "internal_server_error" ) ;
clearTimeout ( this . _timeout ) ; // remove timer
clearInterval ( this . _interval ) ;
clearInterval ( this . _inQto ) ;
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , 'session-terminate' ) ) ;
this . _connected = false ;
this . oDbg . log ( "Disconnected." , 1 ) ;
this . _handleEvent ( 'ondisconnect' ) ;
return false ;
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
this . mechs = new Object ( ) ;
var lMec1 = doc . getElementsByTagName ( "mechanisms" ) ;
this . has _sasl = false ;
for ( var i = 0 ; i < lMec1 . length ; i ++ )
if ( lMec1 . item ( i ) . getAttribute ( "xmlns" ) ==
"urn:ietf:params:xml:ns:xmpp-sasl" ) {
this . has _sasl = true ;
var lMec2 = lMec1 . item ( i ) . getElementsByTagName ( "mechanism" ) ;
for ( var j = 0 ; j < lMec2 . length ; j ++ )
this . mechs [ lMec2 . item ( j ) . firstChild . nodeValue ] = true ;
break ;
}
if ( this . has _sasl )
this . oDbg . log ( "SASL detected" , 2 ) ;
else {
this . oDbg . log ( "No support for SASL detected" , 2 ) ;
return false ;
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
// Get the server CAPS (if available)
this . server _caps = null ;
var sCaps = doc . getElementsByTagName ( "c" ) ;
for ( var i = 0 ; i < sCaps . length ; i ++ ) {
var c _sCaps = sCaps . item ( i ) ;
var x _sCaps = c _sCaps . getAttribute ( "xmlns" ) ;
var v _sCaps = c _sCaps . getAttribute ( "ver" ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
if ( ( x _sCaps == NS _CAPS ) && v _sCaps ) {
this . server _caps = v _sCaps ;
break ;
}
}
2014-04-08 20:14:28 +02:00
// Get legacy session capability if available
this . legacy _sessions = null ;
2014-11-25 20:12:58 +01:00
if ( doc . getElementsByTagName ( "session" ) ) {
2014-04-08 20:14:28 +02:00
this . legacy _sessions = true ;
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
return true ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _process = function ( timerval ) {
if ( ! this . connected ( ) ) {
this . oDbg . log ( "Connection lost ..." , 1 ) ;
if ( this . _interval )
clearInterval ( this . _interval ) ;
return ;
}
this . setPollInterval ( timerval ) ;
if ( this . _timeout )
clearTimeout ( this . _timeout ) ;
var slot = this . _getFreeSlot ( ) ;
if ( slot < 0 )
return ;
if ( typeof ( this . _req [ slot ] ) != 'undefined' &&
typeof ( this . _req [ slot ] . r ) != 'undefined' &&
this . _req [ slot ] . r . readyState != 4 ) {
this . oDbg . log ( "Slot " + slot + " is not ready" ) ;
return ;
}
if ( ! this . isPolling ( ) && this . _pQueue . length == 0 &&
this . _req [ ( slot + 1 ) % 2 ] && this . _req [ ( slot + 1 ) % 2 ] . r . readyState != 4 ) {
this . oDbg . log ( "all slots busy, standby ..." , 2 ) ;
return ;
}
if ( ! this . isPolling ( ) )
this . oDbg . log ( "Found working slot at " + slot , 2 ) ;
this . _req [ slot ] = this . _setupRequest ( true ) ;
/* setup onload handler for async send */
this . _req [ slot ] . r . onreadystatechange =
JSJaC . bind ( function ( ) {
if ( this . _req [ slot ] . r . readyState == 4 ) {
this . _setStatus ( 'processing' ) ;
this . oDbg . log ( "async recv: " + this . _req [ slot ] . r . responseText , 4 ) ;
this . _handleResponse ( this . _req [ slot ] ) ;
if ( ! this . connected ( ) )
return ;
// schedule next tick
if ( this . _pQueue . length ) {
this . _timeout = setTimeout ( JSJaC . bind ( this . _process , this ) , 100 ) ;
} else {
this . oDbg . log ( "scheduling next poll in " + this . getPollInterval ( ) +
" msec" , 4 ) ;
this . _timeout = setTimeout ( JSJaC . bind ( this . _process , this ) , this . getPollInterval ( ) ) ;
}
}
} , this ) ;
try {
this . _req [ slot ] . r . onerror =
JSJaC . bind ( function ( ) {
if ( ! this . connected ( ) )
return ;
this . _errcnt ++ ;
this . oDbg . log ( 'XmlHttpRequest error (' + this . _errcnt + ')' , 1 ) ;
if ( this . _errcnt > JSJAC _ERR _COUNT ) {
// abort
this . _abort ( ) ;
return false ;
}
this . _setStatus ( 'onerror_fallback' ) ;
// schedule next tick
setTimeout ( JSJaC . bind ( this . _resume , this ) , this . getPollInterval ( ) ) ;
return false ;
} , this ) ;
} catch ( e ) { } // well ... no onerror property available, maybe we
// can catch the error somewhere else ...
var reqstr = this . _getRequestString ( ) ;
if ( typeof ( this . _rid ) != 'undefined' ) // remember request id if any
this . _req [ slot ] . rid = this . _rid ;
this . oDbg . log ( "sending: " + reqstr , 4 ) ;
this . _req [ slot ] . r . send ( reqstr ) ;
} ;
/ * *
* @ private
2014-04-08 20:14:28 +02:00
@ param { JSJaCPacket } packet The packet to be sent .
@ param { function } cb The callback to be called when response is received .
@ param { any } arg Optional arguments to be passed to 'cb' when executing it .
@ return Whether registering an ID was successful
@ type boolean
* /
JSJaCConnection . prototype . _registerPID = function ( packet , cb , arg ) {
this . oDbg . log ( "registering id for packet " + packet . xml ( ) , 3 ) ;
var id = packet . getID ( ) ;
if ( ! id ) {
this . oDbg . log ( "id missing" , 1 ) ;
return false ;
}
if ( typeof cb != 'function' ) {
this . oDbg . log ( "callback is not a function" , 1 ) ;
return false ;
}
var jid = packet . getTo ( ) || this . jid ;
if ( packet . getTo ( ) == this . domain )
jid = this . jid ;
if ( ! this . _regIDs [ jid ] ) {
this . _regIDs [ jid ] = { } ;
}
if ( this . _regIDs [ jid ] [ id ] != null ) {
this . oDbg . log ( "id already registered: " + id , 1 ) ;
2014-03-12 14:52:47 +01:00
return false ;
2014-04-08 20:14:28 +02:00
}
this . _regIDs [ jid ] [ id ] = {
cb : cb ,
arg : arg ,
ts : Date . now ( )
} ;
this . oDbg . log ( "registered id " + id , 3 ) ;
this . _cleanupRegisteredPIDs ( ) ;
2014-03-12 14:52:47 +01:00
return true ;
} ;
/ * *
2014-04-08 20:14:28 +02:00
* @ private
* /
JSJaCConnection . prototype . _cleanupRegisteredPIDs = function ( ) {
var now = Date . now ( ) ;
for ( var jid in this . _regIDs ) {
if ( this . _regIDs . hasOwnProperty ( jid ) ) {
for ( var id in this . _regIDs [ jid ] ) {
if ( this . _regIDs [ jid ] . hasOwnProperty ( id ) ) {
if ( this . _regIDs [ jid ] [ id ] . ts + JSJAC _REGID _TIMEOUT < now ) {
this . oDbg . log ( "deleting registered id '" + id + "' due to timeout" , 1 ) ;
delete this . _regIDs [ jid ] [ id ] ;
}
}
}
}
}
} ;
/ * *
* Partial function binding sendEmpty to callback
2014-03-12 14:52:47 +01:00
* @ private
* /
JSJaCConnection . prototype . _prepSendEmpty = function ( cb , ctx ) {
return function ( ) {
ctx . _sendEmpty ( JSJaC . bind ( cb , ctx ) ) ;
} ;
} ;
/ * *
* send empty request
* waiting for stream id to be able to proceed with authentication
* @ private
* /
JSJaCConnection . prototype . _sendEmpty = function ( cb ) {
var slot = this . _getFreeSlot ( ) ;
this . _req [ slot ] = this . _setupRequest ( true ) ;
this . _req [ slot ] . r . onreadystatechange =
JSJaC . bind ( function ( ) {
if ( this . _req [ slot ] . r . readyState == 4 ) {
this . oDbg . log ( "async recv: " + this . _req [ slot ] . r . responseText , 4 ) ;
cb ( this . _req [ slot ] . r ) ; // handle response
}
} , this ) ;
if ( typeof ( this . _req [ slot ] . r . onerror ) != 'undefined' ) {
this . _req [ slot ] . r . onerror =
JSJaC . bind ( function ( e ) {
this . oDbg . log ( 'XmlHttpRequest error' , 1 ) ;
return false ;
} , this ) ;
}
var reqstr = this . _getRequestString ( ) ;
this . oDbg . log ( "sending: " + reqstr , 4 ) ;
this . _req [ slot ] . r . send ( reqstr ) ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _sendRaw = function ( xml , cb , arg ) {
if ( cb )
this . _sendRawCallbacks . push ( { fn : cb , arg : arg } ) ;
this . _pQueue . push ( xml ) ;
this . _process ( ) ;
return true ;
} ;
/ * *
* @ private
* /
JSJaCConnection . prototype . _setStatus = function ( status ) {
if ( ! status || status == '' )
return ;
if ( status != this . _status ) { // status changed!
this . _status = status ;
this . _handleEvent ( 'onstatuschanged' , status ) ;
this . _handleEvent ( 'status_changed' , status ) ;
}
} ;
/ * *
* @ fileoverview All stuff related to HTTP Binding
* @ author Stefan Strigler steve @ zeank . in - berlin . de
2014-11-25 20:12:58 +01:00
* @ version 1.3
2014-03-12 14:52:47 +01:00
* /
/ * *
* Instantiates an HTTP Binding session
* @ class Implementation of { @ link
* http : //www.xmpp.org/extensions/xep-0206.html XMPP Over BOSH}
* formerly known as HTTP Binding .
* @ extends JSJaCConnection
* @ constructor
* /
function JSJaCHttpBindingConnection ( oArg ) {
/ * *
* @ ignore
* /
this . base = JSJaCConnection ;
this . base ( oArg ) ;
// member vars
/ * *
* @ private
* /
this . _hold = JSJACHBC _MAX _HOLD ;
/ * *
* @ private
* /
this . _inactivity = 0 ;
/ * *
* @ private
* /
this . _last _requests = new Object ( ) ; // 'hash' storing hold+1 last requests
/ * *
* @ private
* /
this . _last _rid = 0 ; // I know what you did last summer
/ * *
* @ private
* /
this . _min _polling = 0 ;
/ * *
* @ private
* /
this . _pause = 0 ;
/ * *
* @ private
* /
this . _wait = JSJACHBC _MAX _WAIT ;
}
JSJaCHttpBindingConnection . prototype = new JSJaCConnection ( ) ;
/ * *
* Inherit an instantiated HTTP Binding session
* /
JSJaCHttpBindingConnection . prototype . inherit = function ( oArg ) {
if ( oArg . jid ) {
var oJid = new JSJaCJID ( oArg . jid ) ;
this . domain = oJid . getDomain ( ) ;
this . username = oJid . getNode ( ) ;
this . resource = oJid . getResource ( ) ;
} else {
this . domain = oArg . domain || 'localhost' ;
this . username = oArg . username ;
this . resource = oArg . resource ;
}
this . _sid = oArg . sid ;
this . _rid = oArg . rid ;
this . _min _polling = oArg . polling ;
this . _inactivity = oArg . inactivity ;
this . _setHold ( oArg . requests - 1 ) ;
this . setPollInterval ( this . _timerval ) ;
if ( oArg . wait )
this . _wait = oArg . wait ; // for whatever reason
this . _connected = true ;
this . _handleEvent ( 'onconnect' ) ;
this . _interval = setInterval ( JSJaC . bind ( this . _checkQueue , this ) ,
JSJAC _CHECKQUEUEINTERVAL ) ;
this . _inQto = setInterval ( JSJaC . bind ( this . _checkInQ , this ) ,
JSJAC _CHECKINQUEUEINTERVAL ) ;
this . _timeout = setTimeout ( JSJaC . bind ( this . _process , this ) ,
this . getPollInterval ( ) ) ;
} ;
/ * *
* Sets poll interval
* @ param { int } timerval the interval in seconds
* /
JSJaCHttpBindingConnection . prototype . setPollInterval = function ( timerval ) {
if ( timerval && ! isNaN ( timerval ) ) {
if ( ! this . isPolling ( ) )
this . _timerval = 100 ;
else if ( this . _min _polling && timerval < this . _min _polling * 1000 )
this . _timerval = this . _min _polling * 1000 ;
else if ( this . _inactivity && timerval > this . _inactivity * 1000 )
this . _timerval = this . _inactivity * 1000 ;
else
this . _timerval = timerval ;
}
return this . _timerval ;
} ;
/ * *
* whether this session is in polling mode
* @ type boolean
* /
JSJaCHttpBindingConnection . prototype . isPolling = function ( ) { return ( this . _hold == 0 ) } ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _getFreeSlot = function ( ) {
for ( var i = 0 ; i < this . _hold + 1 ; i ++ )
if ( typeof ( this . _req [ i ] ) == 'undefined' || typeof ( this . _req [ i ] . r ) == 'undefined' || this . _req [ i ] . r . readyState == 4 )
return i ;
return - 1 ; // nothing found
} ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _getHold = function ( ) { return this . _hold ; } ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _getRequestString = function ( raw , last ) {
raw = raw || '' ;
var reqstr = '' ;
// check if we're repeating a request
if ( this . _rid <= this . _last _rid && typeof ( this . _last _requests [ this . _rid ] ) != 'undefined' ) // repeat!
reqstr = this . _last _requests [ this . _rid ] . xml ;
else { // grab from queue
var xml = '' ;
while ( this . _pQueue . length ) {
var curNode = this . _pQueue [ 0 ] ;
xml += curNode ;
this . _pQueue = this . _pQueue . slice ( 1 , this . _pQueue . length ) ;
}
reqstr = "<body xml:lang='" + XML _LANG + "' rid='" + this . _rid + "' sid='" + this . _sid + "' xmlns='http://jabber.org/protocol/httpbind' " ;
if ( JSJAC _HAVEKEYS ) {
reqstr += "key='" + this . _keys . getKey ( ) + "' " ;
if ( this . _keys . lastKey ( ) ) {
this . _keys = new JSJaCKeys ( hex _sha1 , this . oDbg ) ;
reqstr += "newkey='" + this . _keys . getKey ( ) + "' " ;
}
}
if ( last )
reqstr += "type='terminate'" ;
else if ( this . _reinit ) {
if ( JSJACHBC _USE _BOSH _VER )
reqstr += "xmpp:restart='true' xmlns:xmpp='urn:xmpp:xbosh' to='" + this . domain + "'" ;
this . _reinit = false ;
}
if ( xml != '' || raw != '' ) {
reqstr += ">" + raw + xml + "</body>" ;
} else {
reqstr += "/>" ;
}
this . _last _requests [ this . _rid ] = new Object ( ) ;
this . _last _requests [ this . _rid ] . xml = reqstr ;
this . _last _rid = this . _rid ;
for ( var i in this . _last _requests )
if ( this . _last _requests . hasOwnProperty ( i ) &&
i < this . _rid - this . _hold )
delete ( this . _last _requests [ i ] ) ; // truncate
}
return reqstr ;
} ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _getInitialRequestString = function ( ) {
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 + "'" ;
if ( this . secure )
reqstr += " secure='" + this . secure + "'" ;
if ( JSJAC _HAVEKEYS ) {
this . _keys = new JSJaCKeys ( hex _sha1 , this . oDbg ) ; // generate first set of keys
key = this . _keys . getKey ( ) ;
reqstr += " newkey='" + key + "'" ;
}
if ( JSJACHBC _USE _BOSH _VER ) {
reqstr += " ver='" + JSJACHBC _BOSH _VERSION + "'" ;
reqstr += " xmlns:xmpp='urn:xmpp:xbosh'" ;
if ( this . authtype == 'sasl' || this . authtype == 'saslanon' )
reqstr += " xmpp:version='1.0'" ;
}
reqstr += "/>" ;
return reqstr ;
} ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _getStreamID = function ( req ) {
this . oDbg . log ( req . responseText , 4 ) ;
if ( ! req . responseXML || ! req . responseXML . documentElement ) {
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , 'service-unavailable' ) ) ;
return ;
}
var body = req . responseXML . documentElement ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
// any session error?
if ( body . getAttribute ( 'type' ) == 'terminate' ) {
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , 'service-unavailable' ) ) ;
return ;
}
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
// extract stream id used for non-SASL authentication
if ( body . getAttribute ( 'authid' ) ) {
this . streamid = body . getAttribute ( 'authid' ) ;
this . oDbg . log ( "got streamid: " + this . streamid , 2 ) ;
}
if ( ! this . _parseStreamFeatures ( body ) ) {
this . _sendEmpty ( JSJaC . bind ( this . _getStreamID , this ) ) ;
return ;
}
this . _timeout = setTimeout ( JSJaC . bind ( this . _process , this ) ,
this . getPollInterval ( ) ) ;
if ( this . register )
this . _doInBandReg ( ) ;
else
this . _doAuth ( ) ;
} ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _getSuspendVars = function ( ) {
return ( 'host,port,secure,_rid,_last_rid,_wait,_min_polling,_inactivity,_hold,_last_requests,_pause' ) . split ( ',' ) ;
} ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _handleInitialResponse = function ( req ) {
try {
// This will throw an error on Mozilla when the connection was refused
this . oDbg . log ( req . getAllResponseHeaders ( ) , 4 ) ;
this . oDbg . log ( req . responseText , 4 ) ;
} catch ( ex ) {
this . oDbg . log ( "No response" , 4 ) ;
}
if ( req . status != 200 || ! req . responseXML ) {
this . oDbg . log ( "initial response broken (status: " + req . status + ")" , 1 ) ;
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , 'service-unavailable' ) ) ;
return ;
}
var body = req . responseXML . documentElement ;
if ( ! body || body . tagName != 'body' || body . namespaceURI != 'http://jabber.org/protocol/httpbind' ) {
this . oDbg . log ( "no body element or incorrect body in initial response" , 1 ) ;
this . _handleEvent ( "onerror" , JSJaCError ( "500" , "wait" , "internal-service-error" ) ) ;
return ;
}
// Check for errors from the server
if ( body . getAttribute ( "type" ) == "terminate" ) {
this . oDbg . log ( "invalid response:\n" + req . responseText , 1 ) ;
clearTimeout ( this . _timeout ) ; // remove timer
this . _connected = false ;
this . oDbg . log ( "Disconnected." , 1 ) ;
this . _handleEvent ( 'ondisconnect' ) ;
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , 'service-unavailable' ) ) ;
return ;
}
// get session ID
this . _sid = body . getAttribute ( 'sid' ) ;
this . oDbg . log ( "got sid: " + this . _sid , 2 ) ;
// get attributes from response body
if ( body . getAttribute ( 'polling' ) )
this . _min _polling = body . getAttribute ( 'polling' ) ;
if ( body . getAttribute ( 'inactivity' ) )
this . _inactivity = body . getAttribute ( 'inactivity' ) ;
if ( body . getAttribute ( 'requests' ) )
this . _setHold ( body . getAttribute ( 'requests' ) - 1 ) ;
this . oDbg . log ( "set hold to " + this . _getHold ( ) , 2 ) ;
if ( body . getAttribute ( 'ver' ) )
this . _bosh _version = body . getAttribute ( 'ver' ) ;
if ( body . getAttribute ( 'maxpause' ) )
this . _pause = Number . min ( body . getAttribute ( 'maxpause' ) , JSJACHBC _MAXPAUSE ) ;
// must be done after response attributes have been collected
this . setPollInterval ( this . _timerval ) ;
/* start sending from queue for not polling connections */
this . _connected = true ;
this . _inQto = setInterval ( JSJaC . bind ( this . _checkInQ , this ) ,
JSJAC _CHECKINQUEUEINTERVAL ) ;
this . _interval = setInterval ( JSJaC . bind ( this . _checkQueue , this ) ,
JSJAC _CHECKQUEUEINTERVAL ) ;
/ * w a i t f o r i n i t i a l s t r e a m r e s p o n s e t o e x t r a c t s t r e a m i d n e e d e d
* for digest auth
* /
this . _getStreamID ( req ) ;
} ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _parseResponse = function ( req ) {
if ( ! this . connected ( ) || ! req )
return null ;
var r = req . r ; // the XmlHttpRequest
try {
if ( r . status == 404 || r . status == 403 ) {
// connection manager killed session
this . _abort ( ) ;
return null ;
}
if ( r . status != 200 || ! r . responseXML ) {
this . _errcnt ++ ;
var errmsg = "invalid response (" + r . status + "):\n" + r . getAllResponseHeaders ( ) + "\n" + r . responseText ;
if ( ! r . responseXML )
errmsg += "\nResponse failed to parse!" ;
this . oDbg . log ( errmsg , 1 ) ;
if ( this . _errcnt > JSJAC _ERR _COUNT ) {
// abort
this . _abort ( ) ;
return null ;
}
if ( this . connected ( ) ) {
this . oDbg . log ( "repeating (" + this . _errcnt + ")" , 1 ) ;
this . _setStatus ( 'proto_error_fallback' ) ;
// schedule next tick
setTimeout ( JSJaC . bind ( this . _resume , this ) ,
this . getPollInterval ( ) ) ;
}
return null ;
}
} catch ( e ) {
this . oDbg . log ( "XMLHttpRequest error: status not available" , 1 ) ;
2014-04-08 20:14:28 +02:00
this . _errcnt ++ ;
if ( this . _errcnt > JSJAC _ERR _COUNT ) {
// abort
this . _abort ( ) ;
} else {
2014-03-12 14:52:47 +01:00
if ( this . connected ( ) ) {
2014-04-08 20:14:28 +02:00
this . oDbg . log ( "repeating (" + this . _errcnt + ")" , 1 ) ;
2014-03-12 14:52:47 +01:00
2014-04-08 20:14:28 +02:00
this . _setStatus ( 'proto_error_fallback' ) ;
2014-03-12 14:52:47 +01:00
2014-04-08 20:14:28 +02:00
// schedule next tick
setTimeout ( JSJaC . bind ( this . _resume , this ) ,
2014-03-12 14:52:47 +01:00
this . getPollInterval ( ) ) ;
}
}
return null ;
}
var body = r . responseXML . documentElement ;
if ( ! body || body . tagName != 'body' ||
2014-04-08 20:14:28 +02:00
body . namespaceURI != 'http://jabber.org/protocol/httpbind' ) {
2014-03-12 14:52:47 +01:00
this . oDbg . log ( "invalid response:\n" + r . responseText , 1 ) ;
clearTimeout ( this . _timeout ) ; // remove timer
clearInterval ( this . _interval ) ;
clearInterval ( this . _inQto ) ;
this . _connected = false ;
this . oDbg . log ( "Disconnected." , 1 ) ;
this . _handleEvent ( 'ondisconnect' ) ;
this . _setStatus ( 'internal_server_error' ) ;
this . _handleEvent ( 'onerror' ,
2014-04-08 20:14:28 +02:00
JSJaCError ( '500' , 'wait' , 'internal-server-error' ) ) ;
2014-03-12 14:52:47 +01:00
return null ;
}
if ( typeof ( req . rid ) != 'undefined' && this . _last _requests [ req . rid ] ) {
if ( this . _last _requests [ req . rid ] . handled ) {
this . oDbg . log ( "already handled " + req . rid , 2 ) ;
return null ;
} else
this . _last _requests [ req . rid ] . handled = true ;
}
// Check for errors from the server
if ( body . getAttribute ( "type" ) == "terminate" ) {
// read condition
var condition = body . getAttribute ( 'condition' ) ;
if ( condition != "item-not-found" ) {
this . oDbg . log ( "session terminated:\n" + r . responseText , 1 ) ;
clearTimeout ( this . _timeout ) ; // remove timer
clearInterval ( this . _interval ) ;
clearInterval ( this . _inQto ) ;
try {
DataStore . removeDB ( MINI _HASH , 'jsjac' , 'state' ) ;
} catch ( e ) { }
this . _connected = false ;
if ( condition == "remote-stream-error" )
if ( body . getElementsByTagName ( "conflict" ) . length > 0 )
this . _setStatus ( "session-terminate-conflict" ) ;
if ( condition == null )
condition = 'session-terminate' ;
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , condition ) ) ;
this . oDbg . log ( "Aborting remaining connections" , 4 ) ;
for ( var i = 0 ; i < this . _hold + 1 ; i ++ ) {
try {
this . _req [ i ] . r . abort ( ) ;
} catch ( e ) { this . oDbg . log ( e , 1 ) ; }
}
this . oDbg . log ( "parseResponse done with terminating" , 3 ) ;
this . oDbg . log ( "Disconnected." , 1 ) ;
this . _handleEvent ( 'ondisconnect' ) ;
} else {
this . _errcnt ++ ;
if ( this . _errcnt > JSJAC _ERR _COUNT )
this . _abort ( ) ;
}
return null ;
}
// no error
this . _errcnt = 0 ;
return r . responseXML . documentElement ;
} ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _reInitStream = function ( cb ) {
// tell http binding to reinit stream with/before next request
this . _reinit = true ;
this . _sendEmpty ( this . _prepReInitStreamWait ( cb ) ) ;
} ;
JSJaCHttpBindingConnection . prototype . _prepReInitStreamWait = function ( cb ) {
return JSJaC . bind ( function ( req ) {
this . _reInitStreamWait ( req , cb ) ;
} , this ) ;
} ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _reInitStreamWait = function ( req , cb ) {
this . oDbg . log ( "checking for stream features" ) ;
var doc = req . responseXML . documentElement ;
this . oDbg . log ( doc ) ;
if ( doc . getElementsByTagNameNS ) {
this . oDbg . log ( "checking with namespace" ) ;
var features = doc . getElementsByTagNameNS ( 'http://etherx.jabber.org/streams' ,
'features' ) . item ( 0 ) ;
if ( features ) {
var bind = features . getElementsByTagNameNS ( 'urn:ietf:params:xml:ns:xmpp-bind' ,
'bind' ) . item ( 0 ) ;
}
} else {
var featuresNL = doc . getElementsByTagName ( 'stream:features' ) ;
for ( var i = 0 , l = featuresNL . length ; i < l ; i ++ ) {
if ( featuresNL . item ( i ) . namespaceURI == 'http://etherx.jabber.org/streams' ||
2014-11-25 23:42:38 +01:00
featuresNL . item ( i ) . getAttribute ( 'xmlns' ) ==
2014-03-12 14:52:47 +01:00
'http://etherx.jabber.org/streams' ) {
var features = featuresNL . item ( i ) ;
break ;
}
}
if ( features ) {
var bind = features . getElementsByTagName ( 'bind' ) ;
for ( var i = 0 , l = bind . length ; i < l ; i ++ ) {
if ( bind . item ( i ) . namespaceURI == 'urn:ietf:params:xml:ns:xmpp-bind' ||
2014-11-25 23:42:38 +01:00
bind . item ( i ) . getAttribute ( 'xmlns' ) ==
2014-03-12 14:52:47 +01:00
'urn:ietf:params:xml:ns:xmpp-bind' ) {
bind = bind . item ( i ) ;
break ;
}
}
}
}
this . oDbg . log ( features ) ;
this . oDbg . log ( bind ) ;
2014-11-25 23:42:38 +01:00
2014-03-12 14:52:47 +01:00
if ( features ) {
if ( bind ) {
cb ( ) ;
} else {
this . oDbg . log ( "no bind feature - giving up" , 1 ) ;
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , "service-unavailable" ) ) ;
this . _connected = false ;
this . oDbg . log ( "Disconnected." , 1 ) ;
this . _handleEvent ( 'ondisconnect' ) ;
}
} else {
// wait
this . _sendEmpty ( this . _prepReInitStreamWait ( cb ) ) ;
}
} ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _resume = function ( ) {
/ * m a k e s u r e t o r e p e a t l a s t r e q u e s t a s w e c a n b e s u r e t h a t
* it had failed ( only if we 're not using the ' pause ' attribute
* /
if ( this . _pause == 0 && this . _rid >= this . _last _rid )
this . _rid = this . _last _rid - 1 ;
this . _process ( ) ;
} ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _setHold = function ( hold ) {
if ( ! hold || isNaN ( hold ) || hold < 0 )
hold = 0 ;
else if ( hold > JSJACHBC _MAX _HOLD )
hold = JSJACHBC _MAX _HOLD ;
this . _hold = hold ;
return this . _hold ;
} ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _setupRequest = function ( async ) {
var req = new Object ( ) ;
var r = XmlHttp . create ( ) ;
try {
r . open ( "POST" , this . _httpbase , async ) ;
r . setRequestHeader ( 'Content-Type' , 'text/xml; charset=utf-8' ) ;
} catch ( e ) { this . oDbg . log ( e , 1 ) ; }
req . r = r ;
this . _rid ++ ;
req . rid = this . _rid ;
return req ;
} ;
/ * *
* @ private
* /
JSJaCHttpBindingConnection . prototype . _suspend = function ( ) {
if ( this . _pause == 0 )
return ; // got nothing to do
var slot = this . _getFreeSlot ( ) ;
// Intentionally synchronous
this . _req [ slot ] = this . _setupRequest ( false ) ;
var reqstr = "<body xml:lang='" + XML _LANG + "' pause='" + this . _pause + "' xmlns='http://jabber.org/protocol/httpbind' sid='" + this . _sid + "' rid='" + this . _rid + "'" ;
if ( JSJAC _HAVEKEYS ) {
reqstr += " key='" + this . _keys . getKey ( ) + "'" ;
if ( this . _keys . lastKey ( ) ) {
this . _keys = new JSJaCKeys ( hex _sha1 , this . oDbg ) ;
reqstr += " newkey='" + this . _keys . getKey ( ) + "'" ;
}
}
reqstr += ">" ;
while ( this . _pQueue . length ) {
var curNode = this . _pQueue [ 0 ] ;
reqstr += curNode ;
this . _pQueue = this . _pQueue . slice ( 1 , this . _pQueue . length ) ;
}
//reqstr += "<presence type='unavailable' xmlns='jabber:client'/>";
reqstr += "</body>" ;
this . oDbg . log ( "Disconnecting: " + reqstr , 4 ) ;
this . _req [ slot ] . r . send ( reqstr ) ;
} ;
/ * *
* @ author Janusz Dziemidowicz rraptorr @ nails . eu . org
* @ fileoverview All stuff related to WebSocket
* < pre >
* The WebSocket protocol is a bit of a mess . Various , incompatible ,
* protocol drafts were implemented in browsers . Fortunately , recently
* a finished protocol was released in RFC6455 . Further description
* assumes RFC6455 WebSocket protocol version .
*
* WebSocket browser support . Current ( November 2012 ) browser status :
* - Chrome 16 + - works properly and supports RFC6455
* - Firefox 16 + - works properly and support RFC6455 ( ealier versions
* have problems with proxies )
* - Opera 12.10 - supports RFC6455 , but does not work at all if a
* proxy is configured ( earlier versions do not support RFC6455 )
* - Internet Explorer 10 + - works properly and supports RFC6455
*
* Due to the above status , this code is currently recommended on
* Chrome 16 + , Firefox 16 + and Internet Explorer 10 + . Using it on
* other browsers is discouraged .
*
* Please also note that some users are only able to connect to ports
* 80 and 443. Port 80 is sometimes intercepted by transparent HTTP
* proxies , which mostly does not support WebSocket , so port 443 is
* the best choice currently ( it does not have to be
* encrypted ) . WebSocket also usually does not work well with reverse
* proxies , be sure to make extensive tests if you use one .
*
* There is no standard for XMPP over WebSocket . However , there is a
* draft ( http : //tools.ietf.org/html/draft-ietf-xmpp-websocket-00) and
* this implementation follows it .
*
* Tested servers :
*
* - node - xmpp - bosh ( https : //github.com/dhruvbird/node-xmpp-bosh) -
* supports RFC6455 and works with no problems since 0.6 . 1 , it also
* transparently uses STARTTLS if necessary
* - wxg ( https : //github.com/Gordin/wxg) - supports RFC6455 and works
* with no problems , but cannot connect to servers requiring
* STARTTLS ( original wxg at https : //github.com/hocken/wxg has some
* issues , that were fixed by Gordin ) .
* - ejabberd - websockets
* ( https : //github.com/superfeedr/ejabberd-websockets) - does not
* support RFC6455 hence it does not work , adapting it to support
* RFC6455 should be quite easy for anyone knowing Erlang ( some work
* in progress can be found on github )
* - Openfire ( http : //www.igniterealtime.org/projects/openfire/) -
* unofficial plugin is available , but it lacks support
* for RFC6455 hence it does not work
* - Apache Vysper ( http : //mina.apache.org/vysper/) - does
* not support RFC6455 hence does not work
* - Tigase ( http : //www.tigase.org/) - works fine since 5.2.0.
* - MongooseIM ( https : //github.com/esl/ejabberd) - a fork of ejabberd
* with support for XMPP over Websockets .
* < / p r e >
* /
/*exported JSJaCWebSocketConnection */
/ * *
* Instantiates a WebSocket session .
* @ class Implementation of { @ link http : //tools.ietf.org/html/draft-ietf-xmpp-websocket-00 | An XMPP Sub-protocol for WebSocket}.
* @ extends JSJaCConnection
* @ constructor
* @ param { Object } oArg connection properties .
* @ param { string } oArg . httpbase WebSocket connection endpoint ( i . e . ws : //localhost:5280)
* @ param { JSJaCDebugger } [ oArg . oDbg ] A reference to a debugger implementing the JSJaCDebugger interface .
* /
function JSJaCWebSocketConnection ( oArg ) {
this . base = JSJaCConnection ;
this . base ( oArg ) ;
this . _ws = null ;
this . registerHandler ( 'onerror' , JSJaC . bind ( this . _cleanupWebSocket , this ) ) ;
}
JSJaCWebSocketConnection . prototype = new JSJaCConnection ( ) ;
JSJaCWebSocketConnection . prototype . _cleanupWebSocket = function ( ) {
if ( this . _ws !== null ) {
this . _ws . onclose = null ;
this . _ws . onerror = null ;
this . _ws . onopen = null ;
this . _ws . onmessage = null ;
this . _ws . close ( ) ;
this . _ws = null ;
}
} ;
/ * *
* Connect to a jabber / XMPP server .
* @ param { Object } oArg The configuration to be used for connecting .
* @ param { string } oArg . domain The domain name of the XMPP service .
* @ param { string } oArg . username The username ( nodename ) to be logged in with .
* @ param { string } oArg . resource The resource to identify the login with .
* @ param { string } oArg . password The user ' s password .
* @ param { string } [ oArg . authzid ] Authorization identity . Used to act as another user , in most cases not needed and rarely supported by servers . If present should be a bare JID ( user @ example . net ) .
* @ param { boolean } [ oArg . allow _plain ] Whether to allow plain text logins .
* @ param { boolean } [ oArg . allow _scram ] Whether to allow SCRAM - SHA - 1 authentication . Please note that it is quite slow , do some testing on all required browsers before enabling .
* @ param { boolean } [ oArg . register ] Whether to register a new account .
* @ param { string } [ oArg . authhost ] The host that handles the actualy authorization . There are cases where this is different from the settings above , e . g . if there 's a service that provides anonymous logins at ' anon . example . org ' .
* @ param { string } [ oArg . authtype ] Must be one of 'sasl' ( default ) , 'nonsasl' , 'saslanon' , or 'anonymous' .
* @ param { string } [ oArg . xmllang ] The requested language for this login . Typically XMPP server try to respond with error messages and the like in this language if available .
* /
JSJaCWebSocketConnection . prototype . connect = function ( oArg ) {
this . _setStatus ( 'connecting' ) ;
this . domain = oArg . domain || 'localhost' ;
this . username = oArg . username ;
this . resource = oArg . resource ;
this . pass = oArg . password || oArg . pass ;
this . authzid = oArg . authzid || '' ;
this . register = oArg . register ;
this . authhost = oArg . authhost || this . domain ;
this . authtype = oArg . authtype || 'sasl' ;
this . jid = this . username + '@' + this . domain ;
this . fulljid = this . jid + '/' + this . resource ;
if ( oArg . allow _plain ) {
this . _allow _plain = oArg . allow _plain ;
} else {
this . _allow _plain = JSJAC _ALLOW _PLAIN ;
}
if ( oArg . allow _scram ) {
this . _allow _scram = oArg . allow _scram ;
} else {
this . _allow _scram = JSJAC _ALLOW _SCRAM ;
}
if ( oArg . xmllang && oArg . xmllang !== '' ) {
this . _xmllang = oArg . xmllang ;
} else {
this . _xmllang = 'en' ;
}
if ( typeof WebSocket === 'undefined' ) {
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , 'service-unavailable' ) ) ;
return ;
}
this . _ws = new WebSocket ( this . _httpbase , 'xmpp' ) ;
this . _ws . onclose = JSJaC . bind ( this . _onclose , this ) ;
this . _ws . onerror = JSJaC . bind ( this . _onerror , this ) ;
this . _ws . onopen = JSJaC . bind ( this . _onopen , this ) ;
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _onopen = function ( ) {
var reqstr = this . _getInitialRequestString ( ) ;
this . oDbg . log ( reqstr , 4 ) ;
this . _ws . onmessage = JSJaC . bind ( this . _handleOpenStream , this ) ;
this . _ws . send ( reqstr ) ;
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _handleOpenStream = function ( event ) {
var open , stream ;
this . oDbg . log ( event . data , 4 ) ;
open = event . data ;
// skip XML prolog if any
open = open . substr ( open . indexOf ( '<stream:stream' ) ) ;
if ( open . substr ( - 2 ) !== '/>' && open . substr ( - 16 ) !== '</stream:stream>' ) {
// some servers send closed opening tag, some not
open += '</stream:stream>' ;
}
stream = this . _parseXml ( open ) ;
if ( ! stream ) {
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , 'service-unavailable' ) ) ;
return ;
}
// extract stream id used for non-SASL authentication
this . streamid = stream . getAttribute ( 'id' ) ;
this . oDbg . log ( 'got streamid: ' + this . streamid , 2 ) ;
this . _ws . onmessage = JSJaC . bind ( this . _handleInitialResponse , this ) ;
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _handleInitialResponse = function ( event ) {
var doc = this . _parseXml ( event . data ) ;
if ( ! this . _parseStreamFeatures ( doc ) ) {
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , 'service-unavailable' ) ) ;
return ;
}
this . _connected = true ;
if ( this . register ) {
this . _doInBandReg ( ) ;
} else {
this . _doAuth ( ) ;
}
} ;
/ * *
* Disconnect from XMPP service
*
* When called upon leaving a page needs to use 'onbeforeunload' event
* as Websocket would be closed already otherwise prior to this call .
* /
JSJaCWebSocketConnection . prototype . disconnect = function ( ) {
this . _setStatus ( 'disconnecting' ) ;
if ( ! this . connected ( ) ) {
return ;
}
this . _connected = false ;
this . oDbg . log ( 'Disconnecting' , 4 ) ;
this . _sendRaw ( '</stream:stream>' , JSJaC . bind ( this . _cleanupWebSocket , this ) ) ;
this . oDbg . log ( 'Disconnected' , 2 ) ;
this . _handleEvent ( 'ondisconnect' ) ;
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _onclose = function ( ) {
this . oDbg . log ( 'websocket closed' , 2 ) ;
if ( this . _status !== 'disconnecting' ) {
this . _connected = false ;
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , 'service-unavailable' ) ) ;
}
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _onerror = function ( ) {
this . oDbg . log ( 'websocket error' , 1 ) ;
this . _connected = false ;
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , 'service-unavailable' ) ) ;
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _onmessage = function ( event ) {
var stanza , node , packet ;
stanza = event . data ;
this . _setStatus ( 'processing' ) ;
if ( ! stanza || stanza === '' ) {
return ;
}
// WebSocket works only on modern browsers, so it is safe to assume
// that namespaceURI and getElementsByTagNameNS are available.
node = this . _parseXml ( stanza ) ;
if ( node . namespaceURI === NS _STREAM && node . localName === 'error' ) {
if ( node . getElementsByTagNameNS ( NS _STREAMS , 'conflict' ) . length > 0 ) {
this . _setStatus ( 'session-terminate-conflict' ) ;
}
this . _connected = false ;
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , 'remote-stream-error' ) ) ;
return ;
}
packet = JSJaCPacket . wrapNode ( node ) ;
if ( ! packet ) {
return ;
}
this . oDbg . log ( 'async recv: ' + event . data , 4 ) ;
this . _handleEvent ( 'packet_in' , packet ) ;
if ( packet . pType && ! this . _handlePID ( packet ) ) {
this . _handleEvent ( packet . pType ( ) + '_in' , packet ) ;
this . _handleEvent ( packet . pType ( ) , packet ) ;
}
} ;
/ * *
* Parse single XML stanza . As proposed in XMPP Sub - protocol for
* WebSocket draft , it assumes that every stanza is sent in a separate
* WebSocket frame , which greatly simplifies parsing .
* @ private
* /
JSJaCWebSocketConnection . prototype . _parseXml = function ( s ) {
var doc ;
this . oDbg . log ( 'Parsing: ' + s , 4 ) ;
try {
doc = XmlDocument . create ( 'stream' , NS _STREAM ) ;
2014-11-25 20:12:58 +01:00
if ( s . trim ( ) == '</stream:stream>' ) {
// Consider session as closed
this . oDbg . log ( "session terminated" , 1 ) ;
clearTimeout ( this . _timeout ) ; // remove timer
clearInterval ( this . _interval ) ;
clearInterval ( this . _inQto ) ;
try {
DataStore . removeDB ( MINI _HASH , 'jsjac' , 'state' ) ;
} catch ( e ) { }
this . _connected = false ;
this . _handleEvent ( 'onerror' , JSJaCError ( '503' , 'cancel' , 'session-terminate' ) ) ;
this . oDbg . log ( "Disconnected." , 1 ) ;
this . _handleEvent ( 'ondisconnect' ) ;
return null ;
} else if ( s . indexOf ( '<stream:stream' ) === - 1 ) {
2014-03-12 14:52:47 +01:00
// Wrap every stanza into stream element, so that XML namespaces work properly.
doc . loadXML ( "<stream:stream xmlns:stream='" + NS _STREAM + "' xmlns='jabber:client'>" + s + "</stream:stream>" ) ;
return doc . documentElement . firstChild ;
} else {
doc . loadXML ( s ) ;
return doc . documentElement ;
}
} catch ( e ) {
this . oDbg . log ( 'Error: ' + e ) ;
this . _connected = false ;
this . _handleEvent ( 'onerror' , JSJaCError ( '500' , 'wait' , 'internal-service-error' ) ) ;
}
return null ;
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _getInitialRequestString = function ( ) {
var streamto , reqstr ;
streamto = this . domain ;
if ( this . authhost ) {
streamto = this . authhost ;
}
reqstr = '<stream:stream to="' + streamto + '" xmlns="jabber:client" xmlns:stream="' + NS _STREAM + '"' ;
if ( this . authtype === 'sasl' || this . authtype === 'saslanon' ) {
reqstr += ' version="1.0"' ;
}
reqstr += '>' ;
return reqstr ;
} ;
JSJaCWebSocketConnection . prototype . send = function ( packet , cb , arg ) {
this . _ws . onmessage = JSJaC . bind ( this . _onmessage , this ) ;
if ( ! packet || ! packet . pType ) {
this . oDbg . log ( 'no packet: ' + packet , 1 ) ;
return false ;
}
if ( ! this . connected ( ) ) {
return false ;
}
// remember id for response if callback present
if ( cb ) {
if ( ! packet . getID ( ) ) {
packet . setID ( 'JSJaCID_' + this . _ID ++ ) ; // generate an ID
}
// register callback with id
2014-04-08 20:14:28 +02:00
this . _registerPID ( packet , cb , arg ) ;
2014-03-12 14:52:47 +01:00
}
try {
this . _handleEvent ( packet . pType ( ) + '_out' , packet ) ;
this . _handleEvent ( 'packet_out' , packet ) ;
this . _ws . send ( packet . xml ( ) ) ;
} catch ( e ) {
this . oDbg . log ( e . toString ( ) , 1 ) ;
return false ;
}
return true ;
} ;
/ * *
* Resuming connections is not supported by WebSocket .
* /
JSJaCWebSocketConnection . prototype . resume = function ( ) {
return false ; // not supported for websockets
} ;
/ * *
* Suspending connections is not supported by WebSocket .
* /
JSJaCWebSocketConnection . prototype . suspend = function ( ) {
return false ; // not supported for websockets
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _doSASLAuthScramSha1S1 = function ( event ) {
var el = this . _parseXml ( event . data ) ;
return JSJaC . bind ( JSJaCConnection . prototype . _doSASLAuthScramSha1S1 , this ) ( el ) ;
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _doSASLAuthScramSha1S2 = function ( event ) {
var el = this . _parseXml ( event . data ) ;
return JSJaC . bind ( JSJaCConnection . prototype . _doSASLAuthScramSha1S2 , this ) ( el ) ;
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _doSASLAuthDigestMd5S1 = function ( event ) {
var el = this . _parseXml ( event . data ) ;
return JSJaC . bind ( JSJaCConnection . prototype . _doSASLAuthDigestMd5S1 , this ) ( el ) ;
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _doSASLAuthDigestMd5S2 = function ( event ) {
var el = this . _parseXml ( event . data ) ;
return JSJaC . bind ( JSJaCConnection . prototype . _doSASLAuthDigestMd5S2 , this ) ( el ) ;
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _doSASLAuthDone = function ( event ) {
var el = this . _parseXml ( event . data ) ;
return JSJaC . bind ( JSJaCConnection . prototype . _doSASLAuthDone , this ) ( el ) ;
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _reInitStream = function ( cb ) {
var reqstr , streamto = this . domain ;
if ( this . authhost ) {
streamto = this . authhost ;
}
reqstr = '<stream:stream xmlns:stream="' + NS _STREAM + '" xmlns="jabber:client" to="' + streamto + '" version="1.0">' ;
this . _sendRaw ( reqstr , cb ) ;
} ;
/ * *
* @ private
* /
JSJaCWebSocketConnection . prototype . _sendRaw = function ( xml , cb , arg ) {
if ( ! this . _ws ) {
// Socket might have been closed already because of an 'onerror'
// event. In this case we'd try to send a closing stream element
// 'ondisconnect' which won't work.
return false ;
}
if ( cb ) {
this . _ws . onmessage = JSJaC . bind ( cb , this , arg ) ;
}
this . _ws . send ( xml ) ;
return true ;
} ;
2014-11-25 20:12:58 +01:00
/*exported JSJaCUtils */
/ * *
* Various utilities put together so that they don ' t pollute global
* name space .
* @ namespace
* /
var JSJaCUtils = {
/ * *
* XOR two strings of equal length .
* @ param { string } s1 first string to XOR .
* @ param { string } s2 second string to XOR .
* @ return { string } s1 ^ s2 .
* /
xor : function ( s1 , s2 ) {
/*jshint bitwise: false */
if ( ! s1 ) {
return s2 ;
}
if ( ! s2 ) {
return s1 ;
}
var result = '' ;
for ( var i = 0 ; i < s1 . length ; i ++ ) {
result += String . fromCharCode ( s1 . charCodeAt ( i ) ^ s2 . charCodeAt ( i ) ) ;
}
return result ;
} ,
/ * *
* Create nonce value of given size .
* @ param { int } size size of the nonce that should be generated .
* @ return { string } generated nonce .
* /
cnonce : function ( size ) {
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ;
var cnonce = '' ;
for ( var i = 0 ; i < size ; i ++ ) {
cnonce += tab . charAt ( Math . round ( Math . random ( new Date ( ) . getTime ( ) ) * ( tab . length - 1 ) ) ) ;
}
return cnonce ;
} ,
/ * *
* Current timestamp .
* @ return Seconds since 1.1 . 1970.
* @ type int
* /
now : function ( ) {
if ( Date . now && typeof Date . now == 'function' ) {
return Date . now ( ) ;
} else {
return new Date ( ) . getTime ( ) ;
}
}
} ;