changeset 63:20da4fb67977

Auth PLAIN as plugin. Several fixes.
author Sonny Piers <sonny.piers@gmail.com>
date Wed, 01 Feb 2012 19:24:41 +0100
parents b1e75cdbb0ad
children d9f5ae0b6d98
files lightstring.js plugins/PLAIN.js
diffstat 2 files changed, 116 insertions(+), 150 deletions(-) [+]
line wrap: on
line diff
--- a/lightstring.js
+++ b/lightstring.js
@@ -126,145 +126,6 @@ Lightstring.Connection = function(aServi
     this.service = aService;
   this.handlers = {};
   this.callbacks = {};
-  this.on('stream:features', function(stanza) {
-    var nodes = stanza.DOM.querySelectorAll('mechanism');
-    //SASL/Auth features
-    if (nodes.length > 0) {
-      this.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)
-        this.send(
-          "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'" +
-               " mechanism='DIGEST-MD5'/>"
-        );
-      else if ('PLAIN' in mechanisms) {
-        var token = btoa(
-          this.jid +
-          '\u0000' +
-          this.jid.node +
-          '\u0000' +
-          this.password
-        );
-        this.send(
-          "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'" +
-               " mechanism='PLAIN'>" + token + "</auth>"
-        );
-      }
-    }
-    //XMPP features
-    else {
-      this.emit('features', stanza);
-      var that = this;
-      //Bind http://xmpp.org/rfcs/rfc3920.html#bind
-      var bind =
-        "<iq type='set' id='"+Lightstring.newId('sendiq:')+"' xmlns='jabber:client'>" +
-          "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>" +
-            (this.jid.resource? "<resource>" + this.jid.resource + "</resource>": "") +
-          "</bind>" +
-        "</iq>";
-      this.send(
-        bind,
-        //Success
-        function(stanza) {
-          //Session http://xmpp.org/rfcs/rfc3921.html#session
-          this.jid = new Lightstring.JID(stanza.DOM.textContent);
-          that.send(
-            "<iq type='set' id='"+Lightstring.newId('sendiq:')+"' xmlns='jabber:client'>" +
-              "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>" +
-            "</iq>",
-            function() {
-              that.emit('connected');
-            }
-          );
-        },
-        //Error
-        function(stanza) {
-          //TODO: Error?
-        }
-      );
-    }
-  });
-  this.on('success', function(stanza) {
-    this.send(
-      "<stream:stream to='" + this.jid.domain + "'" +
-                    " xmlns='jabber:client'" +
-                    " xmlns:stream='http://etherx.jabber.org/streams'" +
-                    " version='1.0'/>"
-    );
-  });
-  this.on('failure', function(stanza) {
-    this.emit('conn-error', stanza.DOM.firstChild.tagName);
-  });
-  this.on('challenge', function(stanza) {
-    //FIXME this is mostly Strophe code
-
-    function _quote(str) {
-      return '"' + str.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
-    };
-
-    var challenge = atob(stanza.DOM.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/' + this.jid.domain;
-    if (host !== null)
-        digest_uri = digest_uri + '/' + host;
-    var A1 = MD5.hash(this.jid.node +
-                      ':' + realm + ':' + this.password) +
-                      ':' + nonce + ':' + cnonce;
-    var A2 = 'AUTHENTICATE:' + digest_uri;
-
-    var responseText = '';
-    responseText += 'username=' + _quote(this.jid.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"';
-    this.send(
-      "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
-        btoa(responseText) +
-      "</response>");
-  });
 };
 Lightstring.Connection.prototype = {
   /**
@@ -319,13 +180,47 @@ Lightstring.Connection.prototype = {
 
       //TODO node-xmpp-bosh sends a self-closing stream:stream tag; it is wrong!
       that.emit('input', stanza);
-      
+
       if(!stanza.DOM)
         return;
 
-      that.emit(stanza.DOM.tagName, stanza);
+
+      var name = stanza.DOM.localName;
+      if (name === 'features') {
+        //SASL mechanisms
+        if (stanza.DOM.firstChild.localName === 'mechanisms') {
+          stanza.mechanisms = [];
+          var nodes = stanza.DOM.querySelectorAll('mechanism');
+          for (var i = 0; i < nodes.length; i++)
+            stanza.mechanisms.push(nodes[i].textContent);
+          that.emit('mechanisms', stanza);
+        }
+        //XMPP features
+        else if (stanza.DOM.firstChild.localName === 'c') {
+          //TODO: stanza.features
+          that.emit('features', stanza);
+        }
+      }
+      else if (name === 'challenge') {
 
-      if (stanza.DOM.tagName === 'iq') {
+
+      }
+      else if (name === 'response') {
+
+
+      }
+      else if (name === 'success') {
+        that.emit('success', stanza);
+      }
+      else if(name === 'stream') {
+
+
+      }
+
+
+
+      //Iq callbacks
+      else if (name === 'iq') {
         var payload = stanza.DOM.firstChild;
         if (payload)
           that.emit('iq/' + payload.namespaceURI + ':' + payload.localName, stanza);
@@ -345,12 +240,6 @@ Lightstring.Connection.prototype = {
           callback.error(stanza);
 
         delete that.callbacks[id];
-
-      //TODO: really needed?
-      } else if (stanza.DOM.tagName === 'message') {
-        var payloads = stanza.DOM.children;
-        for (var i = 0; i < payloads.length; i++)
-          that.emit('message/' + payloads[i].namespaceURI + ':' + payloads[i].localName, stanza);
       }
     });
   },
@@ -424,7 +313,7 @@ Lightstring.Connection.prototype = {
 
       //Methods
       this[name] = {};
-      for (var method in plugins.methods)
+      for (var method in plugin.methods)
         this[name][method].bind(this);
 
       if (plugin.init)
@@ -441,7 +330,7 @@ Lightstring.Connection.prototype = {
     if (!handlers)
       return;
 
-    if (aData.localName !== 'iq') {
+    if (aData && aData.DOM && aData.DOM.localName !== 'iq') {
       for (var i = 0; i < handlers.length; i++)
         handlers[i].call(this, aData);
 
new file mode 100644
--- /dev/null
+++ b/plugins/PLAIN.js
@@ -0,0 +1,77 @@
+'use strict';
+
+/**
+  Copyright (c) 2012, Sonny Piers <sonny at fastmail dot net>
+
+  Permission to use, copy, modify, and/or distribute this software for any
+  purpose with or without fee is hereby granted, provided that the above
+  copyright notice and this permission notice appear in all copies.
+
+  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+Lightstring.plugins['PLAIN'] = {
+  handlers: {
+    'mechanisms': function (stanza) {
+      if(stanza.mechanisms.indexOf('PLAIN') === -1)
+        return;
+
+      var token = btoa(
+        this.jid +
+        '\u0000' +
+        this.jid.node +
+        '\u0000' +
+        this.password
+      );
+      this.send(
+        "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'" +
+             " mechanism='PLAIN'>" + token + "</auth>"
+      );
+    },
+    //TODO twice success event
+    'success': function (stanza) {
+      this.send(
+        "<stream:stream to='" + this.jid.domain + "'" +
+                      " xmlns='jabber:client'" +
+                      " xmlns:stream='http://etherx.jabber.org/streams'" +
+                      " version='1.0'/>"
+      );
+    },
+    'features': function (stanza) {
+      var that = this;
+      //TODO check if bind supported
+      var bind =
+        "<iq type='set' id='"+Lightstring.newId('sendiq:')+"' xmlns='jabber:client'>" +
+          "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>" +
+            (this.jid.resource? "<resource>" + this.jid.resource + "</resource>": "") +
+          "</bind>" +
+        "</iq>";
+      this.send(
+        bind,
+        //Success
+        function(stanza) {
+          //Session http://xmpp.org/rfcs/rfc3921.html#session
+          that.jid = new Lightstring.JID(stanza.DOM.textContent);
+          that.send(
+            "<iq type='set' id='"+Lightstring.newId('sendiq:')+"' xmlns='jabber:client'>" +
+              "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>" +
+            "</iq>",
+            function() {
+              that.emit('connected');
+            }
+          );
+        },
+        //Error
+        function(stanza) {
+          //TODO: Error?
+        }
+      );
+    }
+  }
+};