diff --git a/lib/client.js b/lib/client.js index 471aedc4ac..603142d1ae 100644 --- a/lib/client.js +++ b/lib/client.js @@ -59,26 +59,40 @@ Client.prototype.setup = function(){ */ Client.prototype.connect = function(name){ + var self = this; debug('connecting to namespace %s', name); - if (!this.server.nsps[name]) { - this.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'}); - return; + + function connectNamespace() { + var nsp = self.server.of(name); + if ('/' != name && !self.nsps['/']) { + self.connectBuffer.push(name); + return; + } + + var socket = nsp.add(self, function(){ + self.sockets.push(socket); + self.nsps[nsp.name] = socket; + + if ('/' == nsp.name && self.connectBuffer.length > 0) { + self.connectBuffer.forEach(self.connect, self); + self.connectBuffer = []; + } + }); } - var nsp = this.server.of(name); - if ('/' != name && !this.nsps['/']) { - this.connectBuffer.push(name); + + if (self.server.nsps[name]) { + // Namespace already created, connect + connectNamespace(); return; } - var self = this; - var socket = nsp.add(this, function(){ - self.sockets.push(socket); - self.nsps[nsp.name] = socket; - - if ('/' == nsp.name && self.connectBuffer.length > 0) { - self.connectBuffer.forEach(self.connect, self); - self.connectBuffer = []; + self.server.checkNamespace(name, function(allow) { + if (allow) { + connectNamespace(); + return } + + self.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'}); }); }; diff --git a/lib/index.js b/lib/index.js index 27634923c5..bb84b8bcc8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -43,6 +43,7 @@ function Server(srv, opts){ } opts = opts || {}; this.nsps = {}; + this.nspValidators = []; this.path(opts.path || '/socket.io'); this.serveClient(false !== opts.serveClient); this.adapter(opts.adapter || Adapter); @@ -137,6 +138,53 @@ Server.prototype.set = function(key, val){ return this; }; +/** + * Sets up server middleware to validate incoming namespaces not already created on the server. + * + * @return {Server} self + * @api public + */ + +Server.prototype.useNamespaceValidator = function(fn){ + this.nspValidators.push(fn); + return this; +}; + +/** + * Executes the middleware for an incoming namespace not already created on the server. + * + * @param name of incomming namespace + * @param {Function} last fn call in the middleware + * @api private + */ + +Server.prototype.checkNamespace = function(name, fn){ + var fns = this.nspValidators.slice(0); + if (!fns.length) return fn(false); + + var namespaceAllowed = false; // Deny unknown namespaces by default + + function run(i){ + fns[i](name, function(err, allow){ + // upon error, short-circuit + if (err) return fn(false); + + // if one piece of middleware explicitly denies namespace, short-circuit + if (allow === false) return fn(false); + + namespaceAllowed = namespaceAllowed || allow === true; + + // if no middleware left, summon callback + if (!fns[i + 1]) return fn(namespaceAllowed); + + // go on to next + run(i + 1); + }); + } + + run(0); +}; + /** * Sets the client serving path. * diff --git a/test/socket.io.js b/test/socket.io.js index c7fbe9a387..2114ee98b3 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -817,6 +817,78 @@ describe('socket.io', function(){ }); }); }); + + it('should allow connections to dynamic namespaces', function(done){ + var srv = http(); + var sio = io(srv); + srv.listen(function(){ + var namespace = '/dynamic'; + var dynamic = client(srv,namespace); + sio.useNamespaceValidator(function(nsp, next) { + expect(nsp).to.be(namespace); + next(null, true); + }); + dynamic.on('error', function(err) { + expect().fail(); + }); + dynamic.on('connect', function() { + expect(sio.nsps[namespace]).to.be.a(Namespace); + expect(sio.nsps[namespace].sockets.length).to.be(1); + done(); + }); + }); + }); + + it('should not allow connections to dynamic namespaces if not supported', function(done){ + var srv = http(); + var sio = io(srv); + srv.listen(function(){ + var namespace = '/dynamic'; + sio.useNamespaceValidator(function(nsp, next) { + expect(nsp).to.be(namespace); + next(null, false); + }); + sio.on('connect', function(socket) { + if (socket.nsp.name === namespace) { + expect().fail(); + } + }); + + var dynamic = client(srv,namespace); + dynamic.on('connect', function(){ + expect().fail(); + }); + dynamic.on('error', function(err) { + expect(err).to.be("Invalid namespace"); + done(); + }); + }); + }); + it('should not allow connections to dynamic namespaces if there is an error', function(done){ + var srv = http(); + var sio = io(srv); + srv.listen(function(){ + var namespace = '/dynamic'; + sio.useNamespaceValidator(function(nsp, next) { + expect(nsp).to.be(namespace); + next(new Error(), true); + }); + sio.on('connect', function(socket) { + if (socket.nsp.name === namespace) { + expect().fail(); + } + }); + + var dynamic = client(srv,namespace); + dynamic.on('connect', function(){ + expect().fail(); + }); + dynamic.on('error', function(err) { + expect(err).to.be("Invalid namespace"); + done(); + }); + }); + }); }); describe('socket', function(){