Mercurial > eldonilo > lightstring
changeset 1:96087680669f
Delete base64.js since I don't care about IE support for the moment.
author | Sonny Piers <sonny.piers@gmail.com> |
---|---|
date | Sun, 18 Dec 2011 20:26:33 +0100 |
parents | 96898e3812a5 |
children | f31a75c3b6c8 |
files | XMPP.js base64.js lightstring.js |
diffstat | 3 files changed, 221 insertions(+), 301 deletions(-) [+] |
line wrap: on
line diff
deleted file mode 100644 --- a/XMPP.js +++ /dev/null @@ -1,221 +0,0 @@ -'use strict'; - -var Lightstring = { - NS: {}, - stanza: {}, -}; - -Lightstring.Connection = function (aURL) { - var parser = new DOMParser(); - var serializer = new XMLSerializer(); - this.handlers = {}; - this.iqid = 1024; - this.getNewId = function() { - this.iqid++; - return 'sendiq:'+this.iqid; - }; - this.parse = function(str) { - return parser.parseFromString(str, 'text/xml').documentElement; - }; - this.serialize = function(elm) { - return serializer.serializeToString(elm); - }; - this.connect = function(jid, password) { - this.domain = jid.split('@')[1]; - this.node = jid.split('@')[0]; - this.jid = jid; - this.password = password; - if(typeof WebSocket === 'undefined') - this.socket = new MozWebSocket(aURL); - else - this.socket = new WebSocket(aURL); - - var that = this; - this.socket.addEventListener('open', function() { - that.emit('open'); - that.send("<stream:stream to='"+that.domain+"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' />"); - }); - this.socket.addEventListener('error', function(err) { - that.emit('error'); - }); - this.socket.addEventListener('close', function(close) { - that.emit('close'); - that.emit('disconnected'); - }); - this.socket.addEventListener('message', function(e) { - that.emit('XMLInput', e.data); - var elm = that.parse(e.data); - that.emit('DOMInput', elm); - that.emit(elm.tagName, elm); - - if((elm.tagName === 'iq')) - that.emit(elm.getAttribute('id'), elm); - }); - }; - - - - this.send = function(stanza, callback) { - //FIXME support for E4X - //~ if(typeof stanza === 'xml') { - //~ stanza = stanza.toXMLString(); - //~ } - if(stanza.cnode) { - stanza = stanza.toString(); - //~ console.log(typeof stanza); - } - if(typeof stanza === 'string') { - var str = stanza; - var elm = this.parse(stanza); - } - else { - var elm = stanza; - var str = this.serialize(stanza); - } - - - if(elm.tagName === 'iq') { - var id = elm.getAttribute('id'); - if(!id) { - elm.setAttribute('id', this.getNewId()) - str = this.serialize(elm) - } - if(callback) this.on(elm.getAttribute('id'), callback); - } - - - this.socket.send(str); - this.emit('XMLOutput', str); - this.emit('DOMOutput', elm); - }; - this.disconnect = function() { - this.send('</stream:stream>'); - this.emit('disconnected'); - this.socket.close(); - }; - //FIXME Callbacks sucks, better idea? - this.emit = function(name, data) { - var handlers = this.handlers[name]; - if(!handlers) - return; - - //FIXME Better idea than passing the context as argument? - for(var i=0; i<handlers.length; i++) - handlers[i](data, this); - - if(name.match('sendiq:')) - delete this.handlers[name]; - }; - this.on = function(name, callback) { - if(!this.handlers[name]) - this.handlers[name] = []; - this.handlers[name].push(callback); - }; - //FIXME do this! - this.once = function(name, callback) { - if(!this.handlers[name]) - this.handlers[name] = []; - this.handlers[name].push(callback); - }; - //Internal - this.on('stream:features', function(stanza, that) { - var nodes = stanza.querySelectorAll('mechanism'); - //SASL/Auth features - if(nodes.length > 0) { - that.emit('mechanisms', stanza); - var mechanisms = {}; - for(var i=0; i<nodes.length; i++) - mechanisms[nodes[i].textContent] = true; - - - //FIXME support SCRAM-SHA1 && allow specify method preferences - if('DIGEST-MD5' in mechanisms) - that.send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>"); - else if('PLAIN' in mechanisms) { - var token = btoa(that.jid + "\u0000" + that.jid.split('@')[0] + "\u0000" + that.password); - that.send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>"+token+"</auth>"); - } - } - //XMPP features - else { - that.emit('features', stanza); - //Bind http://xmpp.org/rfcs/rfc3920.html#bind - that.send("<iq type='set' xmlns='jabber:client'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>", function() { - //Session http://xmpp.org/rfcs/rfc3921.html#session - that.send("<iq type='set' xmlns='jabber:client'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>", function() { - that.emit('connected'); - }); - }); - } - }); - //Internal - this.on('success', function(stanza, that) { - that.send("<stream:stream to='"+that.domain+"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' />"); - }); - //Internal - this.on('challenge', function(stanza, that) { - //FIXME this is mostly Strophe code - - function _quote(str) { - return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; - }; - - var challenge = atob(stanza.textContent); - - var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/; - - var cnonce = MD5.hexdigest(Math.random() * 1234567890); - var realm = ""; - var host = null; - var nonce = ""; - var qop = ""; - var matches; - - while (challenge.match(attribMatch)) { - matches = challenge.match(attribMatch); - challenge = challenge.replace(matches[0], ""); - matches[2] = matches[2].replace(/^"(.+)"$/, "$1"); - switch (matches[1]) { - case "realm": - realm = matches[2]; - break; - case "nonce": - nonce = matches[2]; - break; - case "qop": - qop = matches[2]; - break; - case "host": - host = matches[2]; - break; - } - } - - var digest_uri = "xmpp/" + that.domain; - if (host !== null) { - digest_uri = digest_uri + "/" + host; - } - - var A1 = MD5.hash(that.node + - ":" + realm + ":" + that.password) + - ":" + nonce + ":" + cnonce; - var A2 = 'AUTHENTICATE:' + digest_uri; - - var responseText = ""; - responseText += 'username=' + _quote(that.node) + ','; - responseText += 'realm=' + _quote(realm) + ','; - responseText += 'nonce=' + _quote(nonce) + ','; - responseText += 'cnonce=' + _quote(cnonce) + ','; - responseText += 'nc="00000001",'; - responseText += 'qop="auth",'; - responseText += 'digest-uri=' + _quote(digest_uri) + ','; - responseText += 'response=' + _quote( - MD5.hexdigest(MD5.hexdigest(A1) + ":" + - nonce + ":00000001:" + - cnonce + ":auth:" + - MD5.hexdigest(A2))) + ','; - responseText += 'charset="utf-8"'; - - that.send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"+btoa(responseText)+"</response>"); - }); -};
deleted file mode 100644 --- a/base64.js +++ /dev/null @@ -1,80 +0,0 @@ -// This code was written by Tyler Akins and has been placed in the -// public domain. It would be nice if you left this header intact. -// Base64 code from Tyler Akins -- http://rumkin.com - -var Base64 = (function () { - var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - - var obj = { - /** - * Encodes a string in base64 - * @param {String} input The string to encode in base64. - */ - encode: function (input) { - var output = ""; - var chr1, chr2, chr3; - var enc1, enc2, enc3, enc4; - var i = 0; - - do { - chr1 = input.charCodeAt(i++); - chr2 = input.charCodeAt(i++); - chr3 = input.charCodeAt(i++); - - enc1 = chr1 >> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; - - if (isNaN(chr2)) { - enc3 = enc4 = 64; - } else if (isNaN(chr3)) { - enc4 = 64; - } - - output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + - keyStr.charAt(enc3) + keyStr.charAt(enc4); - } while (i < input.length); - - return output; - }, - - /** - * Decodes a base64 string. - * @param {String} input The string to decode. - */ - decode: function (input) { - var output = ""; - var chr1, chr2, chr3; - var enc1, enc2, enc3, enc4; - var i = 0; - - // remove all characters that are not A-Z, a-z, 0-9, +, /, or = - input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); - - do { - enc1 = keyStr.indexOf(input.charAt(i++)); - enc2 = keyStr.indexOf(input.charAt(i++)); - enc3 = keyStr.indexOf(input.charAt(i++)); - enc4 = keyStr.indexOf(input.charAt(i++)); - - chr1 = (enc1 << 2) | (enc2 >> 4); - chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); - chr3 = ((enc3 & 3) << 6) | enc4; - - output = output + String.fromCharCode(chr1); - - if (enc3 != 64) { - output = output + String.fromCharCode(chr2); - } - if (enc4 != 64) { - output = output + String.fromCharCode(chr3); - } - } while (i < input.length); - - return output; - } - }; - - return obj; -})();
new file mode 100644 --- /dev/null +++ b/lightstring.js @@ -0,0 +1,221 @@ +'use strict'; + +var Lightstring = { + NS: {}, + stanza: {}, +}; + +Lightstring.Connection = function (aURL) { + var parser = new DOMParser(); + var serializer = new XMLSerializer(); + this.handlers = {}; + this.iqid = 1024; + this.getNewId = function() { + this.iqid++; + return 'sendiq:'+this.iqid; + }; + this.parse = function(str) { + return parser.parseFromString(str, 'text/xml').documentElement; + }; + this.serialize = function(elm) { + return serializer.serializeToString(elm); + }; + this.connect = function(jid, password) { + this.domain = jid.split('@')[1]; + this.node = jid.split('@')[0]; + this.jid = jid; + this.password = password; + if(typeof WebSocket === 'undefined') + this.socket = new MozWebSocket(aURL); + else + this.socket = new WebSocket(aURL); + + var that = this; + this.socket.addEventListener('open', function() { + that.emit('open'); + that.send("<stream:stream to='"+that.domain+"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' />"); + }); + this.socket.addEventListener('error', function(err) { + that.emit('error'); + }); + this.socket.addEventListener('close', function(close) { + that.emit('close'); + that.emit('disconnected'); + }); + this.socket.addEventListener('message', function(e) { + that.emit('XMLInput', e.data); + var elm = that.parse(e.data); + that.emit('DOMInput', elm); + that.emit(elm.tagName, elm); + + if((elm.tagName === 'iq')) + that.emit(elm.getAttribute('id'), elm); + }); + }; + + + + this.send = function(stanza, callback) { + //FIXME support for E4X + //~ if(typeof stanza === 'xml') { + //~ stanza = stanza.toXMLString(); + //~ } + if(stanza.cnode) { + stanza = stanza.toString(); + //~ console.log(typeof stanza); + } + if(typeof stanza === 'string') { + var str = stanza; + var elm = this.parse(stanza); + } + else { + var elm = stanza; + var str = this.serialize(stanza); + } + + + if(elm.tagName === 'iq') { + var id = elm.getAttribute('id'); + if(!id) { + elm.setAttribute('id', this.getNewId()) + str = this.serialize(elm) + } + if(callback) this.on(elm.getAttribute('id'), callback); + } + + + this.socket.send(str); + this.emit('XMLOutput', str); + this.emit('DOMOutput', elm); + }; + this.disconnect = function() { + this.send('</stream:stream>'); + this.emit('disconnected'); + this.socket.close(); + }; + //FIXME Callbacks sucks, better idea? + this.emit = function(name, data) { + var handlers = this.handlers[name]; + if(!handlers) + return; + + //FIXME Better idea than passing the context as argument? + for(var i=0; i<handlers.length; i++) + handlers[i](data, this); + + if(name.match('sendiq:')) + delete this.handlers[name]; + }; + this.on = function(name, callback) { + if(!this.handlers[name]) + this.handlers[name] = []; + this.handlers[name].push(callback); + }; + //FIXME do this! + this.once = function(name, callback) { + if(!this.handlers[name]) + this.handlers[name] = []; + this.handlers[name].push(callback); + }; + //Internal + this.on('stream:features', function(stanza, that) { + var nodes = stanza.querySelectorAll('mechanism'); + //SASL/Auth features + if(nodes.length > 0) { + that.emit('mechanisms', stanza); + var mechanisms = {}; + for(var i=0; i<nodes.length; i++) + mechanisms[nodes[i].textContent] = true; + + + //FIXME support SCRAM-SHA1 && allow specify method preferences + if('DIGEST-MD5' in mechanisms) + that.send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>"); + else if('PLAIN' in mechanisms) { + var token = btoa(that.jid + "\u0000" + that.jid.split('@')[0] + "\u0000" + that.password); + that.send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>"+token+"</auth>"); + } + } + //XMPP features + else { + that.emit('features', stanza); + //Bind http://xmpp.org/rfcs/rfc3920.html#bind + that.send("<iq type='set' xmlns='jabber:client'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>", function() { + //Session http://xmpp.org/rfcs/rfc3921.html#session + that.send("<iq type='set' xmlns='jabber:client'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>", function() { + that.emit('connected'); + }); + }); + } + }); + //Internal + this.on('success', function(stanza, that) { + that.send("<stream:stream to='"+that.domain+"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' />"); + }); + //Internal + this.on('challenge', function(stanza, that) { + //FIXME this is mostly Strophe code + + function _quote(str) { + return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; + }; + + var challenge = atob(stanza.textContent); + + var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/; + + var cnonce = MD5.hexdigest(Math.random() * 1234567890); + var realm = ""; + var host = null; + var nonce = ""; + var qop = ""; + var matches; + + while (challenge.match(attribMatch)) { + matches = challenge.match(attribMatch); + challenge = challenge.replace(matches[0], ""); + matches[2] = matches[2].replace(/^"(.+)"$/, "$1"); + switch (matches[1]) { + case "realm": + realm = matches[2]; + break; + case "nonce": + nonce = matches[2]; + break; + case "qop": + qop = matches[2]; + break; + case "host": + host = matches[2]; + break; + } + } + + var digest_uri = "xmpp/" + that.domain; + if (host !== null) { + digest_uri = digest_uri + "/" + host; + } + + var A1 = MD5.hash(that.node + + ":" + realm + ":" + that.password) + + ":" + nonce + ":" + cnonce; + var A2 = 'AUTHENTICATE:' + digest_uri; + + var responseText = ""; + responseText += 'username=' + _quote(that.node) + ','; + responseText += 'realm=' + _quote(realm) + ','; + responseText += 'nonce=' + _quote(nonce) + ','; + responseText += 'cnonce=' + _quote(cnonce) + ','; + responseText += 'nc="00000001",'; + responseText += 'qop="auth",'; + responseText += 'digest-uri=' + _quote(digest_uri) + ','; + responseText += 'response=' + _quote( + MD5.hexdigest(MD5.hexdigest(A1) + ":" + + nonce + ":00000001:" + + cnonce + ":auth:" + + MD5.hexdigest(A2))) + ','; + responseText += 'charset="utf-8"'; + + that.send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"+btoa(responseText)+"</response>"); + }); +};