UNPKG

strophe.js

Version:

Strophe.js is an XMPP library for JavaScript

1,747 lines (1,506 loc) 183 kB
import { btoa as btoa$1, atob as atob$1 } from 'abab'; var global$1 = typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}; /* * This module provides uniform * Shims APIs and globals that are not present in all JS environments, * the most common example for Strophe being browser APIs like WebSocket * and DOM that don't exist under nodejs. * * Usually these will be supplied in nodejs by conditionally requiring a * NPM module that provides a compatible implementation. */ /* global global */ /** * WHATWG WebSockets API * https://www.w3.org/TR/websockets/ * * Interface to use the web socket protocol * * Used implementations: * - supported browsers: built-in in WebSocket global * https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Browser_compatibility * - nodejs: use standard-compliant 'ws' module * https://www.npmjs.com/package/ws */ function getWebSocketImplementation() { let WebSocketImplementation = global$1.WebSocket; if (typeof WebSocketImplementation === 'undefined') { try { WebSocketImplementation = require('ws'); } catch (err) { throw new Error('You must install the "ws" package to use Strophe in nodejs.'); } } return WebSocketImplementation; } const WebSocket = getWebSocketImplementation(); /** * DOMParser * https://w3c.github.io/DOM-Parsing/#the-domparser-interface * * Interface to parse XML strings into Document objects * * Used implementations: * - supported browsers: built-in in DOMParser global * https://developer.mozilla.org/en-US/docs/Web/API/DOMParser#Browser_compatibility * - nodejs: use '@xmldom/xmldom' module * https://www.npmjs.com/package/@xmldom/xmldom */ function getDOMParserImplementation() { let DOMParserImplementation = global$1.DOMParser; if (typeof DOMParserImplementation === 'undefined') { try { DOMParserImplementation = require('@xmldom/xmldom').DOMParser; } catch (err) { throw new Error('You must install the "@xmldom/xmldom" package to use Strophe in nodejs.'); } } return DOMParserImplementation; } const DOMParser = getDOMParserImplementation(); /** * Gets IE xml doc object. Used by getDummyXMLDocument shim. * * Returns: * A Microsoft XML DOM Object * See Also: * http://msdn.microsoft.com/en-us/library/ms757837%28VS.85%29.aspx */ function _getIEXmlDom() { const docStrings = ["Msxml2.DOMDocument.6.0", "Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "MSXML2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"]; for (let d = 0; d < docStrings.length; d++) { try { // eslint-disable-next-line no-undef const doc = new ActiveXObject(docStrings[d]); return doc; } catch (e) {// Try next one } } } /** * Creates a dummy XML DOM document to serve as an element and text node generator. * * Used implementations: * - IE < 10: avoid using createDocument() due to a memory leak, use ie-specific * workaround * - other supported browsers: use document's createDocument * - nodejs: use '@xmldom/xmldom' */ function getDummyXMLDOMDocument() { // nodejs if (typeof document === 'undefined') { try { const DOMImplementation = require('@xmldom/xmldom').DOMImplementation; return new DOMImplementation().createDocument('jabber:client', 'strophe', null); } catch (err) { throw new Error('You must install the "@xmldom/xmldom" package to use Strophe in nodejs.'); } } // IE < 10 if (document.implementation.createDocument === undefined || document.implementation.createDocument && document.documentMode && document.documentMode < 10) { const doc = _getIEXmlDom(); doc.appendChild(doc.createElement('strophe')); return doc; } // All other supported browsers return document.implementation.createDocument('jabber:client', 'strophe', null); } /* * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message * Digest Algorithm, as defined in RFC 1321. * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet * Distributed under the BSD License * See http://pajhome.org.uk/crypt/md5 for more info. */ /* * Everything that isn't used by Strophe has been stripped here! */ /* * Add integers, wrapping at 2^32. This uses 16-bit operations internally * to work around bugs in some JS interpreters. */ const safe_add$1 = function (x, y) { const lsw = (x & 0xFFFF) + (y & 0xFFFF); const msw = (x >> 16) + (y >> 16) + (lsw >> 16); return msw << 16 | lsw & 0xFFFF; }; /* * Bitwise rotate a 32-bit number to the left. */ const bit_rol = function (num, cnt) { return num << cnt | num >>> 32 - cnt; }; /* * Convert a string to an array of little-endian words */ const str2binl = function (str) { if (typeof str !== "string") { throw new Error("str2binl was passed a non-string"); } const bin = []; for (let i = 0; i < str.length * 8; i += 8) { bin[i >> 5] |= (str.charCodeAt(i / 8) & 255) << i % 32; } return bin; }; /* * Convert an array of little-endian words to a string */ const binl2str = function (bin) { let str = ""; for (let i = 0; i < bin.length * 32; i += 8) { str += String.fromCharCode(bin[i >> 5] >>> i % 32 & 255); } return str; }; /* * Convert an array of little-endian words to a hex string. */ const binl2hex = function (binarray) { const hex_tab = "0123456789abcdef"; let str = ""; for (let i = 0; i < binarray.length * 4; i++) { str += hex_tab.charAt(binarray[i >> 2] >> i % 4 * 8 + 4 & 0xF) + hex_tab.charAt(binarray[i >> 2] >> i % 4 * 8 & 0xF); } return str; }; /* * These functions implement the four basic operations the algorithm uses. */ const md5_cmn = function (q, a, b, x, s, t) { return safe_add$1(bit_rol(safe_add$1(safe_add$1(a, q), safe_add$1(x, t)), s), b); }; const md5_ff = function (a, b, c, d, x, s, t) { return md5_cmn(b & c | ~b & d, a, b, x, s, t); }; const md5_gg = function (a, b, c, d, x, s, t) { return md5_cmn(b & d | c & ~d, a, b, x, s, t); }; const md5_hh = function (a, b, c, d, x, s, t) { return md5_cmn(b ^ c ^ d, a, b, x, s, t); }; const md5_ii = function (a, b, c, d, x, s, t) { return md5_cmn(c ^ (b | ~d), a, b, x, s, t); }; /* * Calculate the MD5 of an array of little-endian words, and a bit length */ const core_md5 = function (x, len) { /* append padding */ x[len >> 5] |= 0x80 << len % 32; x[(len + 64 >>> 9 << 4) + 14] = len; let a = 1732584193; let b = -271733879; let c = -1732584194; let d = 271733878; let olda, oldb, oldc, oldd; for (let i = 0; i < x.length; i += 16) { olda = a; oldb = b; oldc = c; 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$1(a, olda); b = safe_add$1(b, oldb); c = safe_add$1(c, oldc); d = safe_add$1(d, oldd); } return [a, b, c, d]; }; /* * These are the functions you'll usually want to call. * They take string arguments and return either hex or base-64 encoded * strings. */ const MD5 = { hexdigest: function (s) { return binl2hex(core_md5(str2binl(s), s.length * 8)); }, hash: function (s) { return binl2str(core_md5(str2binl(s), s.length * 8)); } }; /** Class: Strophe.SASLMechanism * * Encapsulates an SASL authentication mechanism. * * User code may override the priority for each mechanism or disable it completely. * See <priority> for information about changing priority and <test> for informatian on * how to disable a mechanism. * * By default, all mechanisms are enabled and the priorities are * * SCRAM-SHA-1 - 60 * PLAIN - 50 * OAUTHBEARER - 40 * X-OAUTH2 - 30 * ANONYMOUS - 20 * EXTERNAL - 10 * * See: Strophe.Connection.addSupportedSASLMechanisms */ class SASLMechanism { /** * PrivateConstructor: Strophe.SASLMechanism * SASL auth mechanism abstraction. * * Parameters: * (String) name - SASL Mechanism name. * (Boolean) isClientFirst - If client should send response first without challenge. * (Number) priority - Priority. * * Returns: * A new Strophe.SASLMechanism object. */ constructor(name, isClientFirst, priority) { /** PrivateVariable: mechname * Mechanism name. */ this.mechname = name; /** PrivateVariable: isClientFirst * If client sends response without initial server challenge. */ this.isClientFirst = isClientFirst; /** Variable: priority * Determines which <SASLMechanism> is chosen for authentication (Higher is better). * Users may override this to prioritize mechanisms differently. * * Example: (This will cause Strophe to choose the mechanism that the server sent first) * * > Strophe.SASLPlain.priority = Strophe.SASLSHA1.priority; * * See <SASL mechanisms> for a list of available mechanisms. * */ this.priority = priority; } /** * Function: test * Checks if mechanism able to run. * To disable a mechanism, make this return false; * * To disable plain authentication run * > Strophe.SASLPlain.test = function() { * > return false; * > } * * See <SASL mechanisms> for a list of available mechanisms. * * Parameters: * (Strophe.Connection) connection - Target Connection. * * Returns: * (Boolean) If mechanism was able to run. */ test() { // eslint-disable-line class-methods-use-this return true; } /** PrivateFunction: onStart * Called before starting mechanism on some connection. * * Parameters: * (Strophe.Connection) connection - Target Connection. */ onStart(connection) { this._connection = connection; } /** PrivateFunction: onChallenge * Called by protocol implementation on incoming challenge. * * By deafult, if the client is expected to send data first (isClientFirst === true), * this method is called with `challenge` as null on the first call, * unless `clientChallenge` is overridden in the relevant subclass. * * Parameters: * (Strophe.Connection) connection - Target Connection. * (String) challenge - current challenge to handle. * * Returns: * (String) Mechanism response. */ onChallenge(connection, challenge) { // eslint-disable-line throw new Error("You should implement challenge handling!"); } /** PrivateFunction: clientChallenge * Called by the protocol implementation if the client is expected to send * data first in the authentication exchange (i.e. isClientFirst === true). * * Parameters: * (Strophe.Connection) connection - Target Connection. * * Returns: * (String) Mechanism response. */ clientChallenge(connection) { if (!this.isClientFirst) { throw new Error("clientChallenge should not be called if isClientFirst is false!"); } return this.onChallenge(connection); } /** PrivateFunction: onFailure * Protocol informs mechanism implementation about SASL failure. */ onFailure() { this._connection = null; } /** PrivateFunction: onSuccess * Protocol informs mechanism implementation about SASL success. */ onSuccess() { this._connection = null; } } class SASLAnonymous extends SASLMechanism { /** PrivateConstructor: SASLAnonymous * SASL ANONYMOUS authentication. */ constructor(mechname = 'ANONYMOUS', isClientFirst = false, priority = 20) { super(mechname, isClientFirst, priority); } test(connection) { // eslint-disable-line class-methods-use-this return connection.authcid === null; } } class SASLExternal extends SASLMechanism { /** PrivateConstructor: SASLExternal * SASL EXTERNAL authentication. * * The EXTERNAL mechanism allows a client to request the server to use * credentials established by means external to the mechanism to * authenticate the client. The external means may be, for instance, * TLS services. */ constructor(mechname = 'EXTERNAL', isClientFirst = true, priority = 10) { super(mechname, isClientFirst, priority); } onChallenge(connection) { // eslint-disable-line class-methods-use-this /** According to XEP-178, an authzid SHOULD NOT be presented when the * authcid contained or implied in the client certificate is the JID (i.e. * authzid) with which the user wants to log in as. * * To NOT send the authzid, the user should therefore set the authcid equal * to the JID when instantiating a new Strophe.Connection object. */ return connection.authcid === connection.authzid ? '' : connection.authzid; } } const utils = { utf16to8: function (str) { var i, c; var out = ""; var len = str.length; for (i = 0; i < len; i++) { c = str.charCodeAt(i); if (c >= 0x0000 && c <= 0x007F) { out += str.charAt(i); } else if (c > 0x07FF) { out += String.fromCharCode(0xE0 | c >> 12 & 0x0F); out += String.fromCharCode(0x80 | c >> 6 & 0x3F); out += String.fromCharCode(0x80 | c >> 0 & 0x3F); } else { out += String.fromCharCode(0xC0 | c >> 6 & 0x1F); out += String.fromCharCode(0x80 | c >> 0 & 0x3F); } } return out; }, addCookies: function (cookies) { /* Parameters: * (Object) cookies - either a map of cookie names * to string values or to maps of cookie values. * * For example: * { "myCookie": "1234" } * * or: * { "myCookie": { * "value": "1234", * "domain": ".example.org", * "path": "/", * "expires": expirationDate * } * } * * These values get passed to Strophe.Connection via * options.cookies */ cookies = cookies || {}; for (const cookieName in cookies) { if (Object.prototype.hasOwnProperty.call(cookies, cookieName)) { let expires = ''; let domain = ''; let path = ''; const cookieObj = cookies[cookieName]; const isObj = typeof cookieObj === "object"; const cookieValue = escape(unescape(isObj ? cookieObj.value : cookieObj)); if (isObj) { expires = cookieObj.expires ? ";expires=" + cookieObj.expires : ''; domain = cookieObj.domain ? ";domain=" + cookieObj.domain : ''; path = cookieObj.path ? ";path=" + cookieObj.path : ''; } document.cookie = cookieName + '=' + cookieValue + expires + domain + path; } } } }; class SASLOAuthBearer extends SASLMechanism { /** PrivateConstructor: SASLOAuthBearer * SASL OAuth Bearer authentication. */ constructor(mechname = 'OAUTHBEARER', isClientFirst = true, priority = 40) { super(mechname, isClientFirst, priority); } test(connection) { // eslint-disable-line class-methods-use-this return connection.pass !== null; } onChallenge(connection) { // eslint-disable-line class-methods-use-this let auth_str = 'n,'; if (connection.authcid !== null) { auth_str = auth_str + 'a=' + connection.authzid; } auth_str = auth_str + ','; auth_str = auth_str + "\u0001"; auth_str = auth_str + 'auth=Bearer '; auth_str = auth_str + connection.pass; auth_str = auth_str + "\u0001"; auth_str = auth_str + "\u0001"; return utils.utf16to8(auth_str); } } class SASLPlain extends SASLMechanism { /** PrivateConstructor: SASLPlain * SASL PLAIN authentication. */ constructor(mechname = 'PLAIN', isClientFirst = true, priority = 50) { super(mechname, isClientFirst, priority); } test(connection) { // eslint-disable-line class-methods-use-this return connection.authcid !== null; } onChallenge(connection) { // eslint-disable-line class-methods-use-this const { authcid, authzid, domain, pass } = connection; if (!domain) { throw new Error("SASLPlain onChallenge: domain is not defined!"); } // Only include authzid if it differs from authcid. // See: https://tools.ietf.org/html/rfc6120#section-6.3.8 let auth_str = authzid !== `${authcid}@${domain}` ? authzid : ''; auth_str = auth_str + "\u0000"; auth_str = auth_str + authcid; auth_str = auth_str + "\u0000"; auth_str = auth_str + pass; return utils.utf16to8(auth_str); } } /* * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined * in FIPS PUB 180-1 * Version 2.1a 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. */ /* global define */ /* Some functions and variables have been stripped for use with Strophe */ /* * 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 * 8); } 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 * 8); 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 = 255; for (var i = 0; i < str.length * 8; i += 8) { bin[i >> 5] |= (str.charCodeAt(i / 8) & mask) << 24 - i % 32; } return bin; } /* * 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 += "="; } else { str += tab.charAt(triplet >> 6 * (3 - j) & 0x3F); } } } return str; } /* * Convert an array of big-endian words to a string */ function binb2str(bin) { var str = ""; var mask = 255; for (var i = 0; i < bin.length * 32; i += 8) { str += String.fromCharCode(bin[i >> 5] >>> 24 - i % 32 & mask); } return str; } /* * These are the functions you'll usually want to call * They take string arguments and return either hex or base-64 encoded strings */ const SHA1 = { b64_hmac_sha1: function (key, data) { return binb2b64(core_hmac_sha1(key, data)); }, b64_sha1: function (s) { return binb2b64(core_sha1(str2binb(s), s.length * 8)); }, binb2str: binb2str, core_hmac_sha1: core_hmac_sha1, str_hmac_sha1: function (key, data) { return binb2str(core_hmac_sha1(key, data)); }, str_sha1: function (s) { return binb2str(core_sha1(str2binb(s), s.length * 8)); } }; class SASLSHA1 extends SASLMechanism { /** PrivateConstructor: SASLSHA1 * SASL SCRAM SHA 1 authentication. */ constructor(mechname = 'SCRAM-SHA-1', isClientFirst = true, priority = 60) { super(mechname, isClientFirst, priority); } test(connection) { // eslint-disable-line class-methods-use-this return connection.authcid !== null; } onChallenge(connection, challenge) { // eslint-disable-line class-methods-use-this let nonce, salt, iter, Hi, U, U_old, i, k; let responseText = "c=biws,"; let authMessage = `${connection._sasl_data["client-first-message-bare"]},${challenge},`; const cnonce = connection._sasl_data.cnonce; const attribMatch = /([a-z]+)=([^,]+)(,|$)/; while (challenge.match(attribMatch)) { const matches = challenge.match(attribMatch); challenge = challenge.replace(matches[0], ""); switch (matches[1]) { case "r": nonce = matches[2]; break; case "s": salt = matches[2]; break; case "i": iter = matches[2]; break; } } if (nonce.substr(0, cnonce.length) !== cnonce) { connection._sasl_data = {}; return connection._sasl_failure_cb(); } responseText += "r=" + nonce; authMessage += responseText; salt = atob(salt); salt += "\x00\x00\x00\x01"; const pass = utils.utf16to8(connection.pass); Hi = U_old = SHA1.core_hmac_sha1(pass, salt); for (i = 1; i < iter; i++) { U = SHA1.core_hmac_sha1(pass, SHA1.binb2str(U_old)); for (k = 0; k < 5; k++) { Hi[k] ^= U[k]; } U_old = U; } Hi = SHA1.binb2str(Hi); const clientKey = SHA1.core_hmac_sha1(Hi, "Client Key"); const serverKey = SHA1.str_hmac_sha1(Hi, "Server Key"); const clientSignature = SHA1.core_hmac_sha1(SHA1.str_sha1(SHA1.binb2str(clientKey)), authMessage); connection._sasl_data["server-signature"] = SHA1.b64_hmac_sha1(serverKey, authMessage); for (k = 0; k < 5; k++) { clientKey[k] ^= clientSignature[k]; } responseText += ",p=" + btoa(SHA1.binb2str(clientKey)); return responseText; } clientChallenge(connection, test_cnonce) { // eslint-disable-line class-methods-use-this const cnonce = test_cnonce || MD5.hexdigest("" + Math.random() * 1234567890); let auth_str = "n=" + utils.utf16to8(connection.authcid); auth_str += ",r="; auth_str += cnonce; connection._sasl_data.cnonce = cnonce; connection._sasl_data["client-first-message-bare"] = auth_str; auth_str = "n,," + auth_str; return auth_str; } } class SASLXOAuth2 extends SASLMechanism { /** PrivateConstructor: SASLXOAuth2 * SASL X-OAuth2 authentication. */ constructor(mechname = 'X-OAUTH2', isClientFirst = true, priority = 30) { super(mechname, isClientFirst, priority); } test(connection) { // eslint-disable-line class-methods-use-this return connection.pass !== null; } onChallenge(connection) { // eslint-disable-line class-methods-use-this let auth_str = '\u0000'; if (connection.authcid !== null) { auth_str = auth_str + connection.authzid; } auth_str = auth_str + "\u0000"; auth_str = auth_str + connection.pass; return utils.utf16to8(auth_str); } } /* This program is distributed under the terms of the MIT license. Please see the LICENSE file for details. Copyright 2006-2018, OGG, LLC */ /** Function: $build * Create a Strophe.Builder. * This is an alias for 'new Strophe.Builder(name, attrs)'. * * Parameters: * (String) name - The root element name. * (Object) attrs - The attributes for the root element in object notation. * * Returns: * A new Strophe.Builder object. */ function $build(name, attrs) { return new Strophe.Builder(name, attrs); } /** Function: $msg * Create a Strophe.Builder with a <message/> element as the root. * * Parameters: * (Object) attrs - The <message/> element attributes in object notation. * * Returns: * A new Strophe.Builder object. */ function $msg(attrs) { return new Strophe.Builder("message", attrs); } /** Function: $iq * Create a Strophe.Builder with an <iq/> element as the root. * * Parameters: * (Object) attrs - The <iq/> element attributes in object notation. * * Returns: * A new Strophe.Builder object. */ function $iq(attrs) { return new Strophe.Builder("iq", attrs); } /** Function: $pres * Create a Strophe.Builder with a <presence/> element as the root. * * Parameters: * (Object) attrs - The <presence/> element attributes in object notation. * * Returns: * A new Strophe.Builder object. */ function $pres(attrs) { return new Strophe.Builder("presence", attrs); } /** Class: Strophe * An object container for all Strophe library functions. * * This class is just a container for all the objects and constants * used in the library. It is not meant to be instantiated, but to * provide a namespace for library objects, constants, and functions. */ const Strophe = { /** Constant: VERSION */ VERSION: "1.4.4", /** Constants: XMPP Namespace Constants * Common namespace constants from the XMPP RFCs and XEPs. * * NS.HTTPBIND - HTTP BIND namespace from XEP 124. * NS.BOSH - BOSH namespace from XEP 206. * NS.CLIENT - Main XMPP client namespace. * NS.AUTH - Legacy authentication namespace. * NS.ROSTER - Roster operations namespace. * NS.PROFILE - Profile namespace. * NS.DISCO_INFO - Service discovery info namespace from XEP 30. * NS.DISCO_ITEMS - Service discovery items namespace from XEP 30. * NS.MUC - Multi-User Chat namespace from XEP 45. * NS.SASL - XMPP SASL namespace from RFC 3920. * NS.STREAM - XMPP Streams namespace from RFC 3920. * NS.BIND - XMPP Binding namespace from RFC 3920 and RFC 6120. * NS.SESSION - XMPP Session namespace from RFC 3920. * NS.XHTML_IM - XHTML-IM namespace from XEP 71. * NS.XHTML - XHTML body namespace from XEP 71. */ NS: { HTTPBIND: "http://jabber.org/protocol/httpbind", BOSH: "urn:xmpp:xbosh", CLIENT: "jabber:client", AUTH: "jabber:iq:auth", ROSTER: "jabber:iq:roster", PROFILE: "jabber:iq:profile", DISCO_INFO: "http://jabber.org/protocol/disco#info", DISCO_ITEMS: "http://jabber.org/protocol/disco#items", MUC: "http://jabber.org/protocol/muc", SASL: "urn:ietf:params:xml:ns:xmpp-sasl", STREAM: "http://etherx.jabber.org/streams", FRAMING: "urn:ietf:params:xml:ns:xmpp-framing", BIND: "urn:ietf:params:xml:ns:xmpp-bind", SESSION: "urn:ietf:params:xml:ns:xmpp-session", VERSION: "jabber:iq:version", STANZAS: "urn:ietf:params:xml:ns:xmpp-stanzas", XHTML_IM: "http://jabber.org/protocol/xhtml-im", XHTML: "http://www.w3.org/1999/xhtml" }, /** Constants: XHTML_IM Namespace * contains allowed tags, tag attributes, and css properties. * Used in the createHtml function to filter incoming html into the allowed XHTML-IM subset. * See http://xmpp.org/extensions/xep-0071.html#profile-summary for the list of recommended * allowed tags and their attributes. */ XHTML: { tags: ['a', 'blockquote', 'br', 'cite', 'em', 'img', 'li', 'ol', 'p', 'span', 'strong', 'ul', 'body'], attributes: { 'a': ['href'], 'blockquote': ['style'], 'br': [], 'cite': ['style'], 'em': [], 'img': ['src', 'alt', 'style', 'height', 'width'], 'li': ['style'], 'ol': ['style'], 'p': ['style'], 'span': ['style'], 'strong': [], 'ul': ['style'], 'body': [] }, css: ['background-color', 'color', 'font-family', 'font-size', 'font-style', 'font-weight', 'margin-left', 'margin-right', 'text-align', 'text-decoration'], /** Function: XHTML.validTag * * Utility method to determine whether a tag is allowed * in the XHTML_IM namespace. * * XHTML tag names are case sensitive and must be lower case. */ validTag(tag) { for (let i = 0; i < Strophe.XHTML.tags.length; i++) { if (tag === Strophe.XHTML.tags[i]) { return true; } } return false; }, /** Function: XHTML.validAttribute * * Utility method to determine whether an attribute is allowed * as recommended per XEP-0071 * * XHTML attribute names are case sensitive and must be lower case. */ validAttribute(tag, attribute) { if (typeof Strophe.XHTML.attributes[tag] !== 'undefined' && Strophe.XHTML.attributes[tag].length > 0) { for (let i = 0; i < Strophe.XHTML.attributes[tag].length; i++) { if (attribute === Strophe.XHTML.attributes[tag][i]) { return true; } } } return false; }, validCSS(style) { for (let i = 0; i < Strophe.XHTML.css.length; i++) { if (style === Strophe.XHTML.css[i]) { return true; } } return false; } }, /** Constants: Connection Status Constants * Connection status constants for use by the connection handler * callback. * * Status.ERROR - An error has occurred * Status.CONNECTING - The connection is currently being made * Status.CONNFAIL - The connection attempt failed * Status.AUTHENTICATING - The connection is authenticating * Status.AUTHFAIL - The authentication attempt failed * Status.CONNECTED - The connection has succeeded * Status.DISCONNECTED - The connection has been terminated * Status.DISCONNECTING - The connection is currently being terminated * Status.ATTACHED - The connection has been attached * Status.REDIRECT - The connection has been redirected * Status.CONNTIMEOUT - The connection has timed out */ Status: { ERROR: 0, CONNECTING: 1, CONNFAIL: 2, AUTHENTICATING: 3, AUTHFAIL: 4, CONNECTED: 5, DISCONNECTED: 6, DISCONNECTING: 7, ATTACHED: 8, REDIRECT: 9, CONNTIMEOUT: 10, BINDREQUIRED: 11, ATTACHFAIL: 12 }, ErrorCondition: { BAD_FORMAT: "bad-format", CONFLICT: "conflict", MISSING_JID_NODE: "x-strophe-bad-non-anon-jid", NO_AUTH_MECH: "no-auth-mech", UNKNOWN_REASON: "unknown" }, /** Constants: Log Level Constants * Logging level indicators. * * LogLevel.DEBUG - Debug output * LogLevel.INFO - Informational output * LogLevel.WARN - Warnings * LogLevel.ERROR - Errors * LogLevel.FATAL - Fatal errors */ LogLevel: { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, FATAL: 4 }, /** PrivateConstants: DOM Element Type Constants * DOM element types. * * ElementType.NORMAL - Normal element. * ElementType.TEXT - Text data element. * ElementType.FRAGMENT - XHTML fragment element. */ ElementType: { NORMAL: 1, TEXT: 3, CDATA: 4, FRAGMENT: 11 }, /** PrivateConstants: Timeout Values * Timeout values for error states. These values are in seconds. * These should not be changed unless you know exactly what you are * doing. * * TIMEOUT - Timeout multiplier. A waiting request will be considered * failed after Math.floor(TIMEOUT * wait) seconds have elapsed. * This defaults to 1.1, and with default wait, 66 seconds. * SECONDARY_TIMEOUT - Secondary timeout multiplier. In cases where * Strophe can detect early failure, it will consider the request * failed if it doesn't return after * Math.floor(SECONDARY_TIMEOUT * wait) seconds have elapsed. * This defaults to 0.1, and with default wait, 6 seconds. */ TIMEOUT: 1.1, SECONDARY_TIMEOUT: 0.1, /** Function: addNamespace * This function is used to extend the current namespaces in * Strophe.NS. It takes a key and a value with the key being the * name of the new namespace, with its actual value. * For example: * Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub"); * * Parameters: * (String) name - The name under which the namespace will be * referenced under Strophe.NS * (String) value - The actual namespace. */ addNamespace(name, value) { Strophe.NS[name] = value; }, /** Function: forEachChild * Map a function over some or all child elements of a given element. * * This is a small convenience function for mapping a function over * some or all of the children of an element. If elemName is null, all * children will be passed to the function, otherwise only children * whose tag names match elemName will be passed. * * Parameters: * (XMLElement) elem - The element to operate on. * (String) elemName - The child element tag name filter. * (Function) func - The function to apply to each child. This * function should take a single argument, a DOM element. */ forEachChild(elem, elemName, func) { for (let i = 0; i < elem.childNodes.length; i++) { const childNode = elem.childNodes[i]; if (childNode.nodeType === Strophe.ElementType.NORMAL && (!elemName || this.isTagEqual(childNode, elemName))) { func(childNode); } } }, /** Function: isTagEqual * Compare an element's tag name with a string. * * This function is case sensitive. * * Parameters: * (XMLElement) el - A DOM element. * (String) name - The element name. * * Returns: * true if the element's tag name matches _el_, and false * otherwise. */ isTagEqual(el, name) { return el.tagName === name; }, /** PrivateVariable: _xmlGenerator * _Private_ variable that caches a DOM document to * generate elements. */ _xmlGenerator: null, /** Function: xmlGenerator * Get the DOM document to generate elements. * * Returns: * The currently used DOM document. */ xmlGenerator() { if (!Strophe._xmlGenerator) { Strophe._xmlGenerator = getDummyXMLDOMDocument(); } return Strophe._xmlGenerator; }, /** Function: xmlElement * Create an XML DOM element. * * This function creates an XML DOM element correctly across all * implementations. Note that these are not HTML DOM elements, which * aren't appropriate for XMPP stanzas. * * Parameters: * (String) name - The name for the element. * (Array|Object) attrs - An optional array or object containing * key/value pairs to use as element attributes. The object should * be in the format {'key': 'value'} or {key: 'value'}. The array * should have the format [['key1', 'value1'], ['key2', 'value2']]. * (String) text - The text child data for the element. * * Returns: * A new XML DOM element. */ xmlElement(name) { if (!name) { return null; } const node = Strophe.xmlGenerator().createElement(name); // FIXME: this should throw errors if args are the wrong type or // there are more than two optional args for (let a = 1; a < arguments.length; a++) { const arg = arguments[a]; if (!arg) { continue; } if (typeof arg === "string" || typeof arg === "number") { node.appendChild(Strophe.xmlTextNode(arg)); } else if (typeof arg === "object" && typeof arg.sort === "function") { for (let i = 0; i < arg.length; i++) { const attr = arg[i]; if (typeof attr === "object" && typeof attr.sort === "function" && attr[1] !== undefined && attr[1] !== null) { node.setAttribute(attr[0], attr[1]); } } } else if (typeof arg === "object") { for (const k in arg) { if (Object.prototype.hasOwnProperty.call(arg, k) && arg[k] !== undefined && arg[k] !== null) { node.setAttribute(k, arg[k]); } } } } return node; }, /* Function: xmlescape * Excapes invalid xml characters. * * Parameters: * (String) text - text to escape. * * Returns: * Escaped text. */ xmlescape(text) { text = text.replace(/\&/g, "&amp;"); text = text.replace(/</g, "&lt;"); text = text.replace(/>/g, "&gt;"); text = text.replace(/'/g, "&apos;"); text = text.replace(/"/g, "&quot;"); return text; }, /* Function: xmlunescape * Unexcapes invalid xml characters. * * Parameters: * (String) text - text to unescape. * * Returns: * Unescaped text. */ xmlunescape(text) { text = text.replace(/\&amp;/g, "&"); text = text.replace(/&lt;/g, "<"); text = text.replace(/&gt;/g, ">"); text = text.replace(/&apos;/g, "'"); text = text.replace(/&quot;/g, "\""); return text; }, /** Function: xmlTextNode * Creates an XML DOM text node. * * Provides a cross implementation version of document.createTextNode. * * Parameters: * (String) text - The content of the text node. * * Returns: * A new XML DOM text node. */ xmlTextNode(text) { return Strophe.xmlGenerator().createTextNode(text); }, /** Function: xmlHtmlNode * Creates an XML DOM html node. * * Parameters: * (String) html - The content of the html node. * * Returns: * A new XML DOM text node. */ xmlHtmlNode(html) { let node; //ensure text is escaped if (DOMParser) { const parser = new DOMParser(); node = parser.parseFromString(html, "text/xml"); } else { node = new ActiveXObject("Microsoft.XMLDOM"); node.async = "false"; node.loadXML(html); } return node; }, /** Function: getText * Get the concatenation of all text children of an element. * * Parameters: * (XMLElement) elem - A DOM element. * * Returns: * A String with the concatenated text of all text element children. */ getText(elem) { if (!elem) { return null; } let str = ""; if (elem.childNodes.length === 0 && elem.nodeType === Strophe.ElementType.TEXT) { str += elem.nodeValue; } for (let i = 0; i < elem.childNodes.length; i++) { if (elem.childNodes[i].nodeType === Strophe.ElementType.TEXT) { str += elem.childNodes[i].nodeValue; } } return Strophe.xmlescape(str); }, /** Function: copyElement * Copy an XML DOM element. * * This function copies a DOM element and all its descendants and returns * the new copy. * * Parameters: * (XMLElement) elem - A DOM element. * * Returns: * A new, copied DOM element tree. */ copyElement(elem) { let el; if (elem.nodeType === Strophe.ElementType.NORMAL) { el = Strophe.xmlElement(elem.tagName); for (let i = 0; i < elem.attributes.length; i++) { el.setAttribute(elem.attributes[i].nodeName, elem.attributes[i].value); } for (let i = 0; i < elem.childNodes.length; i++) { el.appendChild(Strophe.copyElement(elem.childNodes[i])); } } else if (elem.nodeType === Strophe.ElementType.TEXT) { el = Strophe.xmlGenerator().createTextNode(elem.nodeValue); } return el; }, /** Function: createHtml * Copy an HTML DOM element into an XML DOM. * * This function copies a DOM element and all its descendants and returns * the new copy. * * Parameters: * (HTMLElement) elem - A DOM element. * * Returns: * A new, copied DOM element tree. */ createHtml(elem) { let el; if (elem.nodeType === Strophe.ElementType.NORMAL) { const tag = elem.nodeName.toLowerCase(); // XHTML tags must be lower case. if (Strophe.XHTML.validTag(tag)) { try { el = Strophe.xmlElement(tag); for (let i = 0; i < Strophe.XHTML.attributes[tag].length; i++) { const attribute = Strophe.XHTML.attributes[tag][i]; let value = elem.getAttribute(attribute); if (typeof value === 'undefined' || value === null || value === '' || value === false || value === 0) { continue; } if (attribute === 'style' && typeof value === 'object' && typeof value.cssText !== 'undefined') { value = value.cssText; // we're dealing with IE, need to get CSS out } // filter out invalid css styles if (attribute === 'style') { const css = []; const cssAttrs = value.split(';'); for (let j = 0; j < cssAttrs.length; j++) { const attr = cssAttrs[j].split(':'); const cssName = attr[0].replace(/^\s*/, "").replace(/\s*$/, "").toLowerCase(); if (Strophe.XHTML.validCSS(cssName)) { const cssValue = attr[1].replace(/^\s*/, "").replace(/\s*$/, ""); css.push(cssName + ': ' + cssValue); } } if (css.length > 0) { value = css.join('; '); el.setAttribute(attribute, value); } } else { el.setAttribute(attribute, value); } } for (let i = 0; i < elem.childNodes.length; i++) { el.appendChild(Strophe.createHtml(elem.childNodes[i])); } } catch (e) { // invalid elements el = Strophe.xmlTextNode(''); } } else { el = Strophe.xmlGenerator().createDocumentFragment(); for (let i = 0; i < elem.childNodes.length; i++) { el.appendChild(Strophe.createHtml(elem.childNodes[i])); } } } else if (elem.nodeType === Strophe.ElementType.FRAGMENT) { el = Strophe.xmlGenerator().createDocumentFragment(); for (let i = 0; i < elem.childNodes.length; i++) { el.appendChild(Strophe.createHtml(elem.childNodes[i])); } } else if (elem.nodeType === Strophe.ElementType.TEXT) { el = Strophe.xmlTextNode(elem.nodeValue); } return el; }, /** Function: escapeNode * Escape the node part (also called local part) of a JID. * * Parameters: * (String) node - A node (or local part). * * Returns: * An escaped node (or local part). */ escapeNode(node) { if (typeof node !== "string") { return node; } return node.replace(/^\s+|\s+$/g, '').replace(/\\/g, "\\5c").replace(/ /g, "\\20").replace(/\"/g, "\\22").replace(/\&/g, "\\26").replace(/\'/g, "\\27").replace(/\//g, "\\2f").replace(/:/g, "\\3a").replace(/</g, "\\3c").replace(/>/g, "\\3e").replace(/@/g, "\\40"); }, /** Function: unescapeNode * Unescape a node part (also called local part) of a JID. * * Parameters: * (String) node - A node (or local part). * * Returns: * An unescaped node (or local part). */ unescapeNode(node) { if (typeof node !== "string") { return node; } return node.replace(/\\20/g, " ").replace(/\\22/g, '"').replace(/\\26/g, "&").replace(/\\27/g, "'").replace(/\\2f/g, "/").replace(/\\3a/g, ":").replace(/\\3c/g, "<").replace(/\\3e/g, ">").replace(/\\40/g, "@").replace(/\\5c/g, "\\"); }, /** Function: getNodeFromJid * Get the node portion of a JID String. * * Parameters: * (String) jid - A JID. * * Returns: * A String containing the node. */ getNodeFromJid(jid) { if (jid.indexOf("@") < 0) { return null; } return jid.split("@")[0]; }, /** Function: getDomainFromJid * Get the domain portion of a JID String. * * Parameters: * (String) jid - A JID. * * Returns: * A String containing the domain. */ getDomainFromJid(jid) { const bare = Strophe.getBareJidFromJid(jid); if (bare.indexOf("@") < 0) { return bare; } else { const parts = bare.split("@"); parts.splice(0, 1); return parts.join('@'); } }, /** Function: getResourceFromJid * Get the resource portion of a JID String. * * Parameters: * (String) jid - A JID. * * Returns: * A String containing the resource. */ getResourceFromJid(jid) { if (!jid) { return null; } const s = jid.split("/"); if (s.length < 2) { return null; } s.splice(0, 1); return s.join('/'); }, /** Function: getBareJidFromJid * Get the bare JID from a JID String. * * Parameters: * (String) jid - A JID. * * Returns: * A String containing the bare JID. */ getBareJidFromJid(jid) { return jid ? jid.split("/")[0] : null; }, /** PrivateFunction: _handleError * _Private_ function that properly logs an error to the console */ _handleError(e) { if (typeof e.stack !== "undefined") { Strophe.fatal(e.stack); } if (e.sourceURL) { Strophe.fatal("error: " + this.handler + " " + e.sourceURL + ":" + e.line + " - " + e.name + ": " + e.message); } else if (e.fileName) { Strophe.fatal("error: " + this.handler + " " + e.fileName + ":" + e.lineNumber + " - " + e.name + ": " + e.message); } else { Strophe.fatal("error: " + e.message); } }, /** Function: log * User overrideable logging function. * * This function is called whenever the Strophe library calls any * of the logging functions. The default implementation of this