# HG changeset patch # User Matthew Wild # Date 1265153460 0 # Node ID 0da83180e97508d8bc1d5679a93f2044742f8f63 Initial commit, hello world! diff -r 000000000000 -r 0da83180e975 sha1.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sha1.js Tue Feb 02 23:31:00 2010 +0000 @@ -0,0 +1,208 @@ +/* + * 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 + * Modifications by Matthew Wild to export functions for CommonJS + * 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 + */ +exports.hex = function (s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));} +exports.b64 = function (s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));} +exports.str = function (s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));} +exports.hex_hmac = function (key, data){ return binb2hex(core_hmac_sha1(key, data));} +exports.b64_hmac = function (key, data){ return binb2b64(core_hmac_sha1(key, data));} +exports.str_hmac = function (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; +} diff -r 000000000000 -r 0da83180e975 xmpp.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xmpp.js Tue Feb 02 23:31:00 2010 +0000 @@ -0,0 +1,319 @@ +// Node libs +var tcp = require("tcp"); + +// External libs +var xml = require("./node-xml"); +var sha1 = require("./sha1"); + +// This lib +var xmpp = exports; + +// Wraps a function so that its 'this' is always 'context' when called +var recontext = function (context, f) { return function () { return f.apply(context, arguments); }; }; + +xmpp.xmlns = { + streams: "http://etherx.jabber.org/streams", + component_accept: "jabber:component:accept" +}; + +xmpp.Status = { + ERROR: 0, + CONNECTING: 1, + CONNFAIL: 2, + AUTHENTICATING: 3, + AUTHFAIL: 4, + CONNECTED: 5, + DISCONNECTED: 6, + DISCONNECTING: 7, +}; + +xmpp.LogLevel = { + DEBUG: 0, + INFO: 1, + WARN: 2, + ERROR: 3, + FATAL: 4 +}; +/** XMPPStream: Takes a parser, eats bytes, fires callbacks on stream events **/ +xmpp.Stream = function (callbacks) +{ + this.callbacks = callbacks; + var stream = this; + var stanza; + this.parser = new xml.SaxParser(function (cb) + { + cb.onStartElementNS(function (tagname, attr_arr, prefix, uri, namespaces) + { + var attr = {xmlns:uri}; + for(var i=0;i 0) + attr["xmlns:"+namespaces[i][0]] = namespaces[i][1]; + if(!stanza) + { + if(stream.opened) + stanza = xmpp.stanza(tagname, attr); + else if(tagname == "stream" && uri == xmpp.xmlns.streams) + { + stream.opened = true; + callbacks.opened(attr); + } + else + { + callbacks.error("no-stream"); + } + } + else + { + stanza.c(tagname, attr); + } + + }); + + cb.onEndElementNS(function(tagname) { + if(stanza) + if(stanza.last_node.length == 1) + { + callbacks.stanza(stanza); + stanza = null; + } + else + stanza.up(); + else + { + stream.opened = false; + callbacks.closed(); + } + }); + + cb.onCharacters(function(chars) { + if(stanza) + stanza.t(chars); + }); + }); + + this.data = function (data) + { + return this.parser.parseString(data); + } + + return this; +}; + + +/** Connection: Takes host/port, manages stream **/ +xmpp.Connection = function (host, port) +{ + this.host = host || "localhost"; + this.port = port || 5347; + + this.socket = tcp.createConnection(); + + this.stream = new xmpp.Stream({ + opened: recontext(this, this._stream_opened), + stanza: recontext(this, this._handle_stanza), + closed: recontext(this, this._stream_closed) + }); + + return this; +}; + +exports.Connection.prototype = { + connect: function (jid, pass, callback) + { + this.jid = jid; + this.password = pass; + this.connect_callback = callback; + + var conn = this; + this.socket.addListener("connect", recontext(this, conn._socket_connected)); + this.socket.addListener("disconnect", recontext(this, conn._socket_disconnected)); + this.socket.addListener("receive", recontext(this, conn._socket_received)); + + // Connect TCP socket + this.socket.connect(this.port, this.host); + + this._setStatus(xmpp.Status.CONNECTING); + }, + + send: function (data) + { + this.debug("SND: "+data); + this.socket.send(data.toString()); + }, + + // Update the status of the connection, call connect_callback + _setStatus: function (status, condition) + { + this.status = status; + this.connect_callback(status, condition); + }, + + // Socket listeners, called on TCP-level events + _socket_connected: function () + { + this.info("CONNECTED."); + this.send(""); + }, + + _socket_disconnected: function (had_error) + { + if(this.status == xmpp.Status.CONNECTING) + this._setStatus(xmpp.Status.CONNFAIL); + elseif(this.status == xmpp.Status.CONNECTED) + this._setStatus(xmpp.Status.DISCONNECTED); + this.info("DISCONNECTED."); + }, + + _socket_received: function (data) + { + this.debug("RCV: "+data); + // Push to parser + this.stream.data(data); + }, + + // Stream listeners, called on XMPP-level events + _stream_opened: function (attr) + { + this.debug("STREAM: opened."); + this._setStatus(xmpp.Status.AUTHENTICATING); + var handshake = sha1.hex(attr.id + this.password); + this.debug("Sending authentication token..."); + this.send(""+handshake+""); + }, + + _handle_stanza: function (stanza) + { + if(stanza.attr.xmlns == xmpp.xmlns.component_accept) + { + if(stanza.name == "handshake") + { + this._setStatus(xmpp.Status.CONNECTED); + } + } + this.debug("STANZA: "+stanza.toString()); + }, + + _stream_closed: function () + { + this.debug("STREAM: closed."); + this.socket.close(); + if(this.status == xmpp.Status.CONNECTING) + this._setStatus(xmpp.status.CONNFAIL); + else + this._setStatus(xmpp.Status.DISCONNECTED); + }, + + _stream_error: function (condition) + { + this._setStatus(xmpp.Status.ERROR, condition); + }, + + // Logging + log: function (level, message) {}, + debug: function (message) { return this.log(xmpp.LogLevel.DEBUG, message); }, + info: function (message) { return this.log(xmpp.LogLevel.INFO , message); }, + warn: function (message) { return this.log(xmpp.LogLevel.WARN , message); }, + error: function (message) { return this.log(xmpp.LogLevel.ERROR, message); }, + fatal: function (message) { return this.log(xmpp.LogLevel.FATAL, message); } + +}; + +function xmlescape(s) +{ + return s.replace(/&/g, "&") + .replace(//g, ">") + .replace(/\"/g, """); +} + +/** StanzaBuilder: Helps create and manipulate XML snippets **/ +xmpp.StanzaBuilder = function (name, attr) +{ + this.name = name; + this.attr = attr || {}; + this.tags = []; + this.children = []; + this.last_node = [this]; + return this; +}; + +xmpp.StanzaBuilder.prototype = { + c: function (name, attr) + { + var s = new xmpp.StanzaBuilder(name, attr); + var parent = this.last_node[this.last_node.length-1]; + parent.tags.push(s); + parent.children.push(s); + this.last_node.push(s); + return this; + }, + + t: function (text) + { + var parent = this.last_node[this.last_node.length-1]; + parent.children.push(text); + return this; + }, + + up: function () + { + this.last_node.pop(); + return this; + }, + + toString: function (top_tag_only) + { + var buf = []; + buf.push("<" + this.name); + for(var attr in this.attr) + { + buf.push(" " + attr + "='" + xmlescape(this.attr[attr]) + "'"); + } + + // Now add children if wanted + if(top_tag_only) + { + buf.push(">"); + } + else if(this.children.length == 0) + { + buf.push("/>"); + } + else + { + buf.push(">"); + for(var i = 0; i"); + } + return buf.join(""); + } +} + +xmpp.stanza = function (name, attr) +{ + return new xmpp.StanzaBuilder(name, attr); +} + +xmpp.message = function (attr) +{ + return xmpp.stanza("message", attr); +} + +xmpp.presence = function (attr) +{ + return xmpp.stanza("presence", attr); +} + +xmpp.iq = function (attr) +{ + return xmpp.stanza("iq", attr); +}