changeset 0:96898e3812a5

initial push
author Sonny Piers <sonny.piers@gmail.com>
date Sun, 18 Dec 2011 19:03:28 +0100
parents
children 96087680669f
files XMPP.js base64.js md5.js plugins.js
diffstat 4 files changed, 834 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/XMPP.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>");
+  });
+};
new file mode 100644
--- /dev/null
+++ b/base64.js
@@ -0,0 +1,80 @@
+// 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/md5.js
@@ -0,0 +1,276 @@
+/*
+ * 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.
+ */
+
+var MD5 = (function () {
+    /*
+     * 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 */
+
+    /*
+     * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+     * to work around bugs in some JS interpreters.
+     */
+    var safe_add = function (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.
+     */
+    var bit_rol = function (num, cnt) {
+        return (num << cnt) | (num >>> (32 - cnt));
+    };
+
+    /*
+     * Convert a string to an array of little-endian words
+     * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
+     */
+    var str2binl = function (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) << (i%32);
+        }
+        return bin;
+    };
+
+    /*
+     * Convert an array of little-endian words to a string
+     */
+    var binl2str = function (bin) {
+        var str = "";
+        var mask = (1 << chrsz) - 1;
+        for(var i = 0; i < bin.length * 32; i += chrsz)
+        {
+            str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
+        }
+        return str;
+    };
+
+    /*
+     * Convert an array of little-endian words to a hex string.
+     */
+    var binl2hex = function (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] >> ((i%4)*8+4)) & 0xF) +
+                hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
+        }
+        return str;
+    };
+
+    /*
+     * Convert an array of little-endian words to a base-64 string
+     */
+    var binl2b64 = function (binarray) {
+        var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+        var str = "";
+        var triplet, j;
+        for(var i = 0; i < binarray.length * 4; i += 3)
+        {
+            triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16) |
+                (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) |
+                ((binarray[i+2 >> 2] >> 8 * ((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;
+    };
+
+    /*
+     * These functions implement the four basic operations the algorithm uses.
+     */
+    var md5_cmn = function (q, a, b, x, s, t) {
+        return safe_add(bit_rol(safe_add(safe_add(a, q),safe_add(x, t)), s),b);
+    };
+
+    var md5_ff = function (a, b, c, d, x, s, t) {
+        return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+    };
+
+    var md5_gg = function (a, b, c, d, x, s, t) {
+        return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+    };
+
+    var md5_hh = function (a, b, c, d, x, s, t) {
+        return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+    };
+
+    var 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
+     */
+    var core_md5 = function (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;
+
+        var olda, oldb, oldc, oldd;
+        for (var 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(a, olda);
+            b = safe_add(b, oldb);
+            c = safe_add(c, oldc);
+            d = safe_add(d, oldd);
+        }
+        return [a, b, c, d];
+    };
+
+
+    /*
+     * Calculate the HMAC-MD5, of a key and some data
+     */
+    var core_hmac_md5 = function (key, data) {
+        var bkey = str2binl(key);
+        if(bkey.length > 16) { bkey = core_md5(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_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
+        return core_md5(opad.concat(hash), 512 + 128);
+    };
+
+    var obj = {
+        /*
+         * These are the functions you'll usually want to call.
+         * They take string arguments and return either hex or base-64 encoded
+         * strings.
+         */
+        hexdigest: function (s) {
+            return binl2hex(core_md5(str2binl(s), s.length * chrsz));
+        },
+
+        b64digest: function (s) {
+            return binl2b64(core_md5(str2binl(s), s.length * chrsz));
+        },
+
+        hash: function (s) {
+            return binl2str(core_md5(str2binl(s), s.length * chrsz));
+        },
+
+        hmac_hexdigest: function (key, data) {
+            return binl2hex(core_hmac_md5(key, data));
+        },
+
+        hmac_b64digest: function (key, data) {
+            return binl2b64(core_hmac_md5(key, data));
+        },
+
+        hmac_hash: function (key, data) {
+            return binl2str(core_hmac_md5(key, data));
+        },
+
+        /*
+         * Perform a simple self-test to see if the VM is working
+         */
+        test: function () {
+            return MD5.hexdigest("abc") === "900150983cd24fb0d6963f7d28e17f72";
+        }
+    };
+
+    return obj;
+})();
new file mode 100644
--- /dev/null
+++ b/plugins.js
@@ -0,0 +1,257 @@
+'use strict';
+
+//
+//Roster
+//
+Lighstring.NS.roster = 'jabber:iq:roster';
+Lighstring.stanza.roster = {
+	'get': function() {
+		return "<iq type='get'><query xmlns='"+Mango.NS.roster+"'/></iq>";
+	},
+	add: function(aAddress, aGroups, aCustomName) {
+		var iq = $iq({type: 'set'}).c('query', {xmlns: Mango.NS.roster}).c('item', {jid: aAddress}).tree();
+		if(aCustomName) iq.querySelector('item').setAttribute(aCustomName);
+		for (var i=0; i<aGroups.length; i++) {
+			if(i === 0) iq.querySelector('item').appendChild(document.createElement('group'));
+			iq.querySelector('group').appendChild(document.createElement(aGroups[i]));
+		}
+		return iq;
+	},
+	remove: function(aAddress) {
+		return $iq({type: 'set'}).c('query', {xmlns: Mango.NS.roster}).c('item', {jid: aAddress, subscription: 'remove'}).tree();
+	}
+};
+Lighstring.getRoster = function(connection, aCallback) {
+	connection.send(this.stanza.roster.get(), function(answer){
+		var contacts = [];
+		answer.querySelectorAll('item').forEach(function(item) {
+			var jid = item.getAttribute('jid');
+			var name = item.getAttribute('name');
+			var groups = item.querySelectorAll('group');
+			var subscription = item.getAttribute('subscription');
+			var contact = {};
+			if(name)
+				contact.name = name;
+			if(jid)
+				contact.jid = jid;
+			if(subscription)
+				contact.subscription = subscription;
+			if(groups.length > 0) {
+				contact.groups = [];
+				groups.forEach(function(group) {
+					contact.groups.push(group.textContent);
+				});
+			}
+
+			contacts.push(contact);
+		});
+		aCallback(contacts);
+	});
+}
+//
+//vCard
+//
+Lighstring.NS.vcard = 'vcard-temp';
+Lighstring.stanza.vcard = {
+	'get': function(aTo) {
+		if(aTo)
+			return "<iq type='get' to='"+aTo+"'><vCard xmlns='"+Mango.NS.vcard+"'/></iq>";
+		else
+			return "<iq type='get'><vCard xmlns='"+Mango.NS.vcard+"'/></iq>";
+	}
+};
+//FIXME we should return a proper vcard, not an XMPP one
+Lighstring.getVcard = function(aConnection, aTo, aCallback) {
+	aConnection.send(Mango.stanza.vcard.get(aTo), function(answer, err){
+		if(answer) {
+			var vcard = answer.querySelector('vCard');
+			if(vcard)
+				aCallback(vcard);
+		}		
+		else
+			aCallback(null);
+	});
+}
+//
+//Disco
+//
+Lighstring.NS['disco#info'] = "http://jabber.org/protocol/disco#info";
+Lighstring.NS['disco#items'] = "http://jabber.org/protocol/disco#items";
+Lighstring.stanza.disco = {
+	items: function(aTo, aNode) {
+		if(aTo)
+			var iq = "<iq type='get' to='"+aTo+"'>";
+		else
+			var iq = "<iq type='get'>";
+		
+		if(aNode)
+			var query = "<query xmlns='"+Mango.NS['disco#items']+"' node='"+aNode+"'/>";
+		else
+			var query = "<query xmlns='"+Mango.NS['disco#items']+"'/>";
+			
+		return iq+query+"</iq>";
+	},
+	info: function(aTo, aNode) {
+		if(aTo)
+			var iq = "<iq type='get' to='"+aTo+"'>";
+		else
+			var iq = "<iq type='get'>";
+		if(aNode)
+			var query = "<query xmlns='"+Mango.NS['disco#info']+"' node='"+aNode+"'/>";
+		else
+			var query = "<query xmlns='"+Mango.NS['disco#info']+"'/>";
+			
+		return iq+query+"</iq>";
+	}
+};
+Lighstring.discoItems = function(aConnection, aTo, aCallback) {
+	aConnection.send(Mango.stanza.disco.items(aTo), function(answer){
+		var items = [];
+		answer.querySelectorAll('item').forEach(function(node) {
+			var item = {
+				jid: node.getAttribute('jid'),
+				name: node.getAttribute('name'),
+				node: node.getAttribute('node')
+			}
+			items.push(item);
+		});
+		if(aCallback)
+			aCallback(items);
+	});
+};
+Lighstring.discoInfo = function(aConnection, aTo, aNode, aCallback) {
+	aConnection.send(Mango.stanza.disco.info(aTo, aNode), function(answer){
+		var field = answer.querySelector('field[var="pubsub#creator"] > value');
+		var creator = field ? field.textContent : '';
+		//FIXME callback the entire data
+		aCallback(creator);
+	});
+};
+//
+//PubSub
+//
+Lighstring.NS.x = "jabber:x:data";
+Lighstring.NS.pubsub = "http://jabber.org/protocol/pubsub";
+Lighstring.NS.pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
+Lighstring.stanza.pubsub = {
+	getConfig: function(aTo, aNode) {
+		return  "<iq type='get' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub_owner+"'><configure node='"+aNode+"'/></pubsub></iq>";
+	},
+	items: function(aTo, aNode) {
+		return  "<iq type='get' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub+"'><items node='"+aNode+"'/></pubsub></iq>";
+	},
+	affiliations: function(aTo, aNode) {
+		return "<iq type='get' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub_owner+"'><affiliations node='"+aNode+"'/></pubsub></iq>";
+	},
+	publish: function(aTo, aNode, aItem, aId) {
+		return  "<iq type='set' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub+"'><publish node='"+aNode+"'><item id='"+aId+"'>"+aItem+"</item></publish></pubsub></iq>";
+	},
+	retract: function(aTo, aNode, aItem) {
+		return  "<iq type='set' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub+"'><retract node='"+aNode+"'><item id='"+aItem+"'/></retract></pubsub></iq>";
+	},
+	'delete': function(aTo, aNode, aURI) {
+		return  "<iq type='set' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub_owner+"'><delete node='"+aNode+"'/></pubsub></iq>";
+	},
+	create: function(aTo, aNode, aFields) {
+		var iq = "<iq type='set' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub+"'><create node='"+aNode+"'/>";
+		if(aFields) {
+			iq += "<configure><x xmlns='"+Mango.NS.x+"' type='submit'>"
+			aFields.forEach(function(field) {
+				iq += field;
+			});
+			iq += "</x></configure>";
+		}
+		iq += "</pubsub></iq>";
+		return iq;
+	},
+	setAffiliations: function(aTo, aNode, aAffiliations) {
+		var iq = "<iq type='set' to='"+aTo+"'><pubsub xmlns='"+Mango.NS.pubsub_owner+"'><affiliations node='"+aNode+"'>";
+		for(var i = 0; i < aAffiliations.length; i++) {
+			iq += "<affiliation jid='"+aAffiliations[i][0]+"' affiliation='"+aAffiliations[i][1]+"'/>"
+		}
+		iq += "</affiliations></pubsub></iq>";
+		return iq;
+	},
+};
+Lighstring.pubsubItems = function(aConnection, aTo, aNode, aCallback) {
+	aConnection.send(Mango.stanza.pubsub.items(aTo, aNode), function(answer){
+		var items = [];
+		answer.querySelectorAll('item').forEach(function(node) {
+			var item = {
+				id: node.getAttribute('id'),
+				name: node.querySelector('title').textContent,
+				src: node.querySelector('content').getAttribute('src'),
+				type: node.querySelector('content').getAttribute('type'),
+			}
+			var thumbnail = node.querySelector('link');
+			if(thumbnail)
+				item.thumbnail = thumbnail.getAttribute('href');
+			items.push(item);
+		})
+		if(aCallback)
+			aCallback(items);
+	});
+}
+Lighstring.pubsubCreate = function(aConnection, aTo, aNode, aFields, aCallback) {
+	aConnection.send(Mango.stanza.pubsub.create(aTo, aNode, aFields), function(answer) {
+		if(answer.getAttribute('type') === 'result')
+			aCallback(null, answer);
+		else
+			aCallback(answer, null);
+	});
+};
+Lighstring.pubsubConfig = function(aConnection, aTo, aNode, aCallback) {
+	aConnection.send(Mango.stanza.pubsub.getConfig(aTo, aNode), function(answer){
+		var accessmodel = answer.querySelector('field[var="pubsub#access_model"]').lastChild.textContent;
+		if(accessmodel)
+			aCallback(accessmodel);
+		else
+			aCallback(null);
+	});
+}
+Lighstring.pubsubRetract = function(aConnection, aTo, aNode, aItem, aCallback) {
+	aConnection.send(Mango.stanza.pubsub.retract(aTo, aNode, aItem), function(answer){
+		if(aCallback)
+			aCallback(answer);
+	});
+}
+Lighstring.pubsubPublish = function(aConnection, aTo, aNode, aItem, aId, aCallback) {
+	aConnection.send(Mango.stanza.pubsub.publish(aTo, aNode, aItem, aId), function(answer){
+		if(answer.getAttribute('type') === 'result')
+			aCallback(null, answer);
+		else
+			aCallback(answer, null);
+	});
+}
+Lighstring.pubsubDelete = function(aConnection, aTo, aNode, aCallback) {
+	aConnection.send(Mango.stanza.pubsub.delete(aTo, aNode), function(answer){
+		if(aCallback)
+			aCallback(answer);
+	});
+}
+Lighstring.pubsubGetAffiliations = function(aConnection, aTo, aNode, aCallback) {
+	aConnection.send(Mango.stanza.pubsub.affiliations(aTo, aNode), function(answer) {
+		if((answer.getAttribute('type') === 'result') && aCallback) {
+			var affiliations = {};
+			answer.querySelectorAll('affiliation').forEach(function(affiliation) {
+				affiliations[affiliation.getAttribute("jid")] = affiliation.getAttribute("affiliation");
+			})
+			aCallback(affiliations);
+		}
+	});
+};
+Lighstring.pubsubSetAffiliations = function(aConnection, aTo, aNode, aAffiliations, aCallback) {
+	aConnection.send(Mango.stanza.pubsub.setAffiliations(aTo, aNode, aAffiliations));
+};
+//
+//IM
+//
+Lighstring.stanza.message = {
+	normal: function(aTo, aSubject, aText) {
+		return "<message type='normal' to='"+aTo+"'><subject>"+aSubject+"</subject><body>"+aText+"</body></message>";
+	},
+	chat: function(aTo, aText) {
+		return "<message type='chat' to='"+aTo+"'><body>"+aText+"</body></message>";
+	}
+};
+