diff --git a/.gitmodules b/.gitmodules index 4d88b912..851d5c96 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "lib/vendor/js-oo"] - path = lib/vendor/js-oo - url = git://github.com/visionmedia/js-oo.git [submodule "example/client"] path = example/client url = git://github.com/LearnBoost/Socket.IO.git diff --git a/example/server.js b/example/server.js index b0cd83d9..1a8b5bf6 100644 --- a/example/server.js +++ b/example/server.js @@ -58,9 +58,7 @@ io.listen(server, { onClientMessage: function(message, client){ var msg = { message: [client.sessionId, message] }; buffer.push(msg); - if (buffer.length > 15) { - buffer.shift(); - } + if (buffer.length > 15) buffer.shift(); client.broadcast(json(msg)); } diff --git a/lib/socket.io/client.js b/lib/socket.io/client.js index c6d92541..b635ee0e 100644 --- a/lib/socket.io/client.js +++ b/lib/socket.io/client.js @@ -1,159 +1,150 @@ -var options = require('./util/options').options, - urlparse = require('url').parse; +var urlparse = require('url').parse, -module.exports = Class({ - - include: [options], - - options: { +Client = module.exports = function(listener, req, res, options, head){ + this.listener = listener; + this.options({ timeout: 12000, closeTimeout: 0 - }, + }, options); + this.connections = 0; + this.connected = false; + this._heartbeats = 0; + this.upgradeHead = head; + this._onConnect(req, res); +}; - init: function(listener, req, res, options, head){ - this.listener = listener; - this.setOptions(options); - this.connections = 0; - this.connected = false; - this._heartbeats = 0; - this.upgradeHead = head; - this._onConnect(req, res); - }, +sys.inherits(Client, require('./utils').options); - send: function(message){ - if (!this.connected || !(this.connection.readyState === 'open' || - this.connection.readyState === 'writeOnly')){ - return this._queue(message); - } - this._write(this._encode(message)); - return this; - }, - - broadcast: function(message){ - if (!('sessionId' in this)) { - return this; - } - this.listener.broadcast(message, this.sessionId); - return this; - }, - - _onMessage: function(data){ - var messages = this._decode(data); - if (messages === false) return this.listener.options.log('Bad message received from client ' + this.sessionId); - for (var i = 0, l = messages.length; i < l; i++){ - if (messages[i].substr(0, 3) == '\ufffdh\ufffd'){ - return this._onHeartbeat(data.substr(3)); - } - this.listener._onClientMessage(messages[i], this); - } - }, - - _onConnect: function(req, res){ - var self = this; - this.request = req; - this.response = res; - this.connection = this.request.connection; - if (this._disconnectTimeout) clearTimeout(this._disconnectTimeout); - }, - - _encode: function(messages){ - var ret = '', - messages = messages instanceof Array ? messages : []; - for (var i = 0, l = messages.length; i < l; i++){ - ret += messages[i].length + message; - } - return '\ufffdm\ufffd' + ret; - }, - - _decode: function(data){ - if (data.substr(0, 3) !== '\ufffdm\ufffd') return false; - var messages = []; - do(){ - for (var i = 0, n, number = '';; i++;){ - var n = data.substr(i, 1); - if (Number(n) != n){ - number = Number(number); - break; - } - number += n; - } - messages.push(data.substr(i, i + number)); // here - data = data.substr(i + number); - } while(data !== ''); - return messages; - }, - - _payload: function(){ - var payload = []; - - this.connections++; - this.connected = true; - - if (!this.handshaked){ - this._generateSessionId(); - payload.push(this.sessionId); - this.handshaked = true; - } - - payload = payload.concat(this._writeQueue || []); - this._writeQueue = []; - - if (payload.length) this._write(this._encode(payload)); - if (this.connections === 1) this.listener._onClientConnect(this); - - if (this.listener.options.timeout) this._heartbeat(); - }, - - _heartbeat: function(){ - var self = this; - this.send('\ufffdh\ufffd' + ++this._heartbeats); - this._heartbitTimeout = setTimeout(function(){ - self.close(); - }, this.listener.options.timeout); - }, - - _onHeartbeat: function(h){ - if (h === this._heartbeats) clearTimeout(this._heartbitTimeout); - }, - - _onClose: function(){ - var self = this; - clearTimeout(this._heartbeatTimeout); - this.connected = false; - this._disconnectTimeout = setTimeout(function(){ - self._onDisconnect(); - }, this.options.closeTimeout); - }, - - _onDisconnect: function(){ - if (!this.finalized){ - this._writeQueue = []; - this.connected = false; - this.finalized = true; - if (this.handshaked) this.listener._onClientDisconnect(this); - } - }, - - _queue: function(message){ - if (!('_writeQueue' in this)){ - this._writeQueue = []; - } - this._writeQueue.push(message); - return this; - }, - - _generateSessionId: function(){ - if (this.sessionId) return this.listener.options.log('This client already has a session id'); - this.sessionId = Math.random().toString().substr(2); - return this; - }, - - _verifyOrigin: function(origin){ - var parts = urlparse(origin), origins = this.listener.options.origins; - return origins.indexOf('*:*') !== -1 || - origins.indexOf(parts.host + ':' + parts.port) !== -1 || - origins.indexOf(parts.host + ':*') !== -1 || - origins.indexOf('*:' + parts.port) !== -1; +Client.prototype.send = function(message){ + if (!this.connected || !(this.connection.readyState === 'open' || + this.connection.readyState === 'writeOnly')){ + return this._queue(message); } + this._write(this._encode(message)); + return this; +}; -}); \ No newline at end of file +Client.prototype.broadcast = function(message){ + if (!('sessionId' in this)) return this; + this.listener.broadcast(message, this.sessionId); + return this; +}; + +Client.prototype._onMessage = function(data){ + var messages = this._decode(data); + if (messages === false) return this.listener.options.log('Bad message received from client ' + this.sessionId); + for (var i = 0, l = messages.length; i < l; i++){ + if (messages[i].substr(0, 3) == '\ufffdh\ufffd'){ + return this._onHeartbeat(data.substr(3)); + } + this.listener._onClientMessage(messages[i], this); + } +}; + +Client.prototype._onConnect = function(req, res){ + var self = this; + this.request = req; + this.response = res; + this.connection = this.request.connection; + if (this._disconnectTimeout) clearTimeout(this._disconnectTimeout); +}; + +Client.prototype._encode = function(messages){ + var ret = '', + messages = messages instanceof Array ? messages : []; + for (var i = 0, l = messages.length; i < l; i++){ + ret += messages[i].length + message; + } + return '\ufffdm\ufffd' + ret; +}; + +Client.prototype._decode = function(data){ + if (data.substr(0, 3) !== '\ufffdm\ufffd') return false; + var messages = []; + do { + for (var i = 0, n, number = '';; i++){ + var n = data.substr(i, 1); + if (Number(n) != n){ + number = Number(number); + break; + } + number += n; + } + messages.push(data.substr(i, i + number)); // here + data = data.substr(i + number); + } while(data !== ''); + return messages; +}; + +Client.prototype._payload = function(){ + var payload = []; + + this.connections++; + this.connected = true; + + if (!this.handshaked){ + this._generateSessionId(); + payload.push(this.sessionId); + this.handshaked = true; + } + + payload = payload.concat(this._writeQueue || []); + this._writeQueue = []; + + if (payload.length) this._write(this._encode(payload)); + if (this.connections === 1) this.listener._onClientConnect(this); + + if (this.listener.options.timeout) this._heartbeat(); +}; + +Client.prototype._heartbeat = function(){ + var self = this; + this.send('\ufffdh\ufffd' + ++this._heartbeats); + this._heartbitTimeout = setTimeout(function(){ + self.close(); + }, this.listener.options.timeout); +}; + +Client.prototype._onHeartbeat = function(h){ + if (h === this._heartbeats) clearTimeout(this._heartbitTimeout); +}; + +Client.prototype._onClose = function(){ + var self = this; + clearTimeout(this._heartbeatTimeout); + this.connected = false; + this._disconnectTimeout = setTimeout(function(){ + self._onDisconnect(); + }, this.options.closeTimeout); +}; + +Client.prototype._onDisconnect = function(){ + if (!this.finalized){ + this._writeQueue = []; + this.connected = false; + this.finalized = true; + if (this.handshaked) this.listener._onClientDisconnect(this); + } +}; + +Client.prototype._queue = function(message){ + if (!('_writeQueue' in this)){ + this._writeQueue = []; + } + this._writeQueue.push(message); + return this; +}; + +Client.prototype._generateSessionId = function(){ + if (this.sessionId) return this.listener.options.log('This client already has a session id'); + this.sessionId = Math.random().toString().substr(2); + return this; +}; + +Client.prototype._verifyOrigin = function(origin){ + var parts = urlparse(origin), origins = this.listener.options.origins; + return origins.indexOf('*:*') !== -1 || + origins.indexOf(parts.host + ':' + parts.port) !== -1 || + origins.indexOf(parts.host + ':*') !== -1 || + origins.indexOf('*:' + parts.port) !== -1; +}; \ No newline at end of file diff --git a/lib/socket.io/listener.js b/lib/socket.io/listener.js index 0bd0d067..f6b34f4e 100644 --- a/lib/socket.io/listener.js +++ b/lib/socket.io/listener.js @@ -1,119 +1,120 @@ var url = require('url'), - options = require('./util/options').options, - Client = require('./client'), - transports = {}; + Client = require('./client'), + transports = { + 'flashsocket': require('./transports/flashsocket'), + 'htmlfile': require('./transports/htmlfile'), + 'websocket': require('./transports/websocket'), + 'xhr-multipart': require('./transports/xhr-multipart'), + 'xhr-polling': require('./transports/xhr-polling') + }, -module.exports = Class({ - - include: [process.EventEmitter.prototype, options], - - options: { +Listener = module.exports = function(server, options){ + var self = this; + process.EventEmitter.call(this); + this.server = server; + this.options({ origins: '*:*', resource: 'socket.io', transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling'], - transportOptions: {}, + transportOptions: { + 'xhr-polling': { + closeTimeout: 5000, + duration: 20000 + } + }, log: function(message){ require('sys').log(message); } - }, - - init: function(server, options){ - var self = this; - process.EventEmitter.call(this); - this.server = server; - this.setOptions(options); - this.clients = []; - this.clientsIndex = {}; - - var listeners = this.server.listeners('request'); - this.server.removeAllListeners('request'); - - this.server.addListener('request', function(req, res){ - if (self.check(req, res)) return; - for (var i = 0; i < listeners.length; i++) { - listeners[i].call(this, req, res); - } - }); - - this.server.addListener('upgrade', function(req, socket, head){ - if (!self.check(req, socket, true, head)){ - socket.destroy(); - } - }); - - this.options.transports.forEach(function(t){ - if (!(t in transports)){ - transports[t] = require('./transports/' + t)[t]; - if (transports[t].init) transports[t].init(this); - } - }, this); - - this.options.log('socket.io ready - accepting connections'); - }, - - broadcast: function(message, except){ - for (var i = 0, l = this.clients.length; i < l; i++){ - if (this.clients[i] && (!except || [].concat(except).indexOf(this.clients[i].sessionId) == -1)){ - this.clients[i].send(message); - } + }, options); + this.clients = []; + this.clientsIndex = {}; + + var listeners = this.server.listeners('request'); + this.server.removeAllListeners('request'); + + this.server.addListener('request', function(req, res){ + if (self.check(req, res)) return; + for (var i = 0; i < listeners.length; i++){ + listeners[i].call(this, req, res); } - return this; - }, - - check: function(req, res, httpUpgrade, head){ - var path = url.parse(req.url).pathname, parts, cn; - if (path.indexOf('/' + this.options.resource) === 0){ - parts = path.substr(1).split('/'); - if (parts[2]){ - cn = this._lookupClient(parts[2]); - if (cn){ - cn._onConnect(req, res); - } else { - req.connection.end(); - this.options.log('Couldnt find client with session id "' + parts[2] + '"'); - } - } else { - this._onConnection(parts[1], req, res, httpUpgrade, head); - } - return true; + }); + + this.server.addListener('upgrade', function(req, socket, head){ + if (!self.check(req, socket, true, head)){ + socket.destroy(); } - return false; - }, + }); - _lookupClient: function(sid){ - return this.clientsIndex[sid]; - }, - - _onClientConnect: function(client){ - if (!(client instanceof Client) || !client.sessionId){ - return this.options.log('Invalid client'); - } - client.i = this.clients.length; - this.clients.push(client); - this.clientsIndex[client.sessionId] = client; - this.options.log('Client '+ client.sessionId +' connected'); - this.emit('clientConnect', client); - }, - - _onClientMessage: function(data, client){ - this.emit('clientMessage', data, client); - }, - - _onClientDisconnect: function(client){ - this.clientsIndex[client.sessionId] = null; - this.clients[client.i] = null; - this.options.log('Client '+ client.sessionId +' disconnected'); - this.emit('clientDisconnect', client); - }, - - // new connections (no session id) - _onConnection: function(transport, req, res, httpUpgrade, head){ - if (this.options.transports.indexOf(transport) === -1 || (httpUpgrade && !transports[transport].httpUpgrade)){ - httpUpgrade ? res.destroy() : req.connection.destroy(); - return this.options.log('Illegal transport "'+ transport +'"'); - } - this.options.log('Initializing client with transport "'+ transport +'"'); - new transports[transport](this, req, res, this.options.transportOptions[transport], head); + for (var i in transports){ + if ('init' in transports[i]) transports[t].init(this); } - -}); + + this.options.log('socket.io ready - accepting connections'); +}; + +sys.inherits(Listener, process.EventEmitter); +sys.inherits(Listener, require('./utils').options); + +Listener.prototype.broadcast = function(message, except){ + for (var i = 0, l = this.clients.length; i < l; i++){ + if (this.clients[i] && (!except || [].concat(except).indexOf(this.clients[i].sessionId) == -1)){ + this.clients[i].send(message); + } + } + return this; +}; + +Listener.prototype.check = function(req, res, httpUpgrade, head){ + var path = url.parse(req.url).pathname, parts, cn; + if (path.indexOf('/' + this.options.resource) === 0){ + parts = path.substr(1).split('/'); + if (parts[2]){ + cn = this._lookupClient(parts[2]); + if (cn){ + cn._onConnect(req, res); + } else { + req.connection.end(); + this.options.log('Couldnt find client with session id "' + parts[2] + '"'); + } + } else { + this._onConnection(parts[1], req, res, httpUpgrade, head); + } + return true; + } + return false; +}; + +Listener.prototype._lookupClient = function(sid){ + return this.clientsIndex[sid]; +}; + +Listener.prototype._onClientConnect = function(client){ + if (!(client instanceof Client) || !client.sessionId){ + return this.options.log('Invalid client'); + } + client.i = this.clients.length; + this.clients.push(client); + this.clientsIndex[client.sessionId] = client; + this.options.log('Client '+ client.sessionId +' connected'); + this.emit('clientConnect', client); +}; + +Listener.prototype._onClientMessage = function(data, client){ + this.emit('clientMessage', data, client); +}; + +Listener.prototype._onClientDisconnect = function(client){ + this.clientsIndex[client.sessionId] = null; + this.clients[client.i] = null; + this.options.log('Client '+ client.sessionId +' disconnected'); + this.emit('clientDisconnect', client); +}; + +Listener.prototype._onConnection = function(transport, req, res, httpUpgrade, head){ + if (this.options.transports.indexOf(transport) === -1 || (httpUpgrade && !transports[transport].httpUpgrade)){ + httpUpgrade ? res.destroy() : req.connection.destroy(); + return this.options.log('Illegal transport "'+ transport +'"'); + } + this.options.log('Initializing client with transport "'+ transport +'"'); + new transports[transport](this, req, res, this.options.transportOptions[transport], head); +}; \ No newline at end of file diff --git a/lib/socket.io/transports/flashsocket.js b/lib/socket.io/transports/flashsocket.js index 9533087a..6e458c20 100644 --- a/lib/socket.io/transports/flashsocket.js +++ b/lib/socket.io/transports/flashsocket.js @@ -1,25 +1,31 @@ -var websocket = require('./websocket').websocket, - net = require('net'), - listeners = []; +var Websocket = require('./websocket'), + net = require('net'), + listeners = [], -exports.flashsocket = websocket.extend({}); -exports.flashsocket.httpUpgrade = true; -exports.flashsocket.init = function(listener){ +Flashsocket = module.exports = function(){}; + +sys.inherits(HTMLFile, Websocket); + +Flashsocket.httpUpgrade = true; + +Flashsocket.init = function(listener){ listeners.push(listener); }; -net.createServer(function(socket){ - socket.write('\n'); - socket.write('\n'); - socket.write('\n'); +try { + net.createServer(function(socket){ + socket.write('\n'); + socket.write('\n'); + socket.write('\n'); - listeners.forEach(function(l){ - [].concat(l.options.origins).forEach(function(origin){ - var parts = origin.split(':'); - socket.write('\n'); + listeners.forEach(function(l){ + [].concat(l.options.origins).forEach(function(origin){ + var parts = origin.split(':'); + socket.write('\n'); + }); }); - }); - socket.write('\n'); - socket.end(); -}).listen(843); + socket.write('\n'); + socket.end(); + }).listen(843); +} catch(e){} \ No newline at end of file diff --git a/lib/socket.io/transports/htmlfile.js b/lib/socket.io/transports/htmlfile.js index ae68ebe7..882051f5 100644 --- a/lib/socket.io/transports/htmlfile.js +++ b/lib/socket.io/transports/htmlfile.js @@ -1,45 +1,45 @@ -var Client = require('../client').Client, - qs = require('querystring'); +var Client = require('../client'), + qs = require('querystring'), -exports.htmlfile = Client.extend({ +HTMLFile = module.exports = function(){}; + +sys.inherits(HTMLFile, Client); - _onConnect: function(req, res){ - var self = this, body = ''; - switch (req.method){ - case 'GET': - this.__super__(req, res); - this.request.connection.addListener('close', function(){ self._onClose(); }); - this.response.useChunkedEncodingByDefault = true; - this.response.shouldKeepAlive = true; - this.response.writeHead(200, { - 'Content-Type': 'text/html', - 'Connection': 'keep-alive', - 'Transfer-Encoding': 'chunked' - }); - if ('flush' in this.response) this.response.flush(); - this._payload(); - break; - - case 'POST': - req.addListener('data', function(message){ - body += message; - }); - req.addListener('end', function(){ - try { - var msg = qs.parse(body); - self._onMessage(msg.data); - } catch(e){} - res.writeHead(200); - res.write('ok'); - res.end(); - }); - break; - } - }, - - _write: function(message){ - this.response.write(''); - if ('flush' in this.response) this.response.flush(); +HTMLFile.prototype._onConnect = function(req, res){ + var self = this, body = ''; + switch (req.method){ + case 'GET': + Client.prototype._onConnect.apply(this, [req, res]); + this.request.connection.addListener('close', function(){ self._onClose(); }); + this.response.useChunkedEncodingByDefault = true; + this.response.shouldKeepAlive = true; + this.response.writeHead(200, { + 'Content-Type': 'text/html', + 'Connection': 'keep-alive', + 'Transfer-Encoding': 'chunked' + }); + if ('flush' in this.response) this.response.flush(); + this._payload(); + break; + + case 'POST': + req.addListener('data', function(message){ + body += message; + }); + req.addListener('end', function(){ + try { + var msg = qs.parse(body); + self._onMessage(msg.data); + } catch(e){} + res.writeHead(200); + res.write('ok'); + res.end(); + }); + break; } +}; -}); \ No newline at end of file +HTMLFile.prototype._write = function(message){ + this.response.write(''); + if ('flush' in this.response) this.response.flush(); +}; \ No newline at end of file diff --git a/lib/socket.io/transports/websocket.js b/lib/socket.io/transports/websocket.js index 26f8bb7c..6b6087b8 100644 --- a/lib/socket.io/transports/websocket.js +++ b/lib/socket.io/transports/websocket.js @@ -1,134 +1,137 @@ -var Client = require('../client').Client, +var Client = require('../client'), url = require('url'), Buffer = require('buffer').Buffer, - crypto = require('crypto'); + crypto = require('crypto'), -exports.websocket = Client.extend({ +WebSocket = exports.websocket = function(){}; - _onConnect: function(req, socket){ - var self = this, headers = []; - this.request = req; - this.connection = socket; - this.data = ''; +sys.inherits(WebSocket, Client); - if (this.request.headers.upgrade !== 'WebSocket' || !this._verifyOrigin(this.request.headers.origin)){ - this.listener.options.log('WebSocket connection invalid'); - this.connection.end(); - } +WebSocket.prototype._onConnect = function(req, socket){ + var self = this, headers = []; + this.request = req; + this.connection = socket; + this.data = ''; - this.connection.setTimeout(0); - this.connection.setEncoding('utf8'); - this.connection.setNoDelay(true); + if (this.request.headers.upgrade !== 'WebSocket' || !this._verifyOrigin(this.request.headers.origin)){ + this.listener.options.log('WebSocket connection invalid'); + this.connection.end(); + } - if ('sec-websocket-key1' in this.request.headers) { - this.draft = 76; - } + this.connection.setTimeout(0); + this.connection.setEncoding('utf8'); + this.connection.setNoDelay(true); - if (this.draft == 76) { + if ('sec-websocket-key1' in this.request.headers){ + this.draft = 76; + } - var origin = this.request.headers.origin; + if (this.draft == 76){ + var origin = this.request.headers.origin; - headers = [ - 'HTTP/1.1 101 WebSocket Protocol Handshake', - 'Upgrade: WebSocket', - 'Connection: Upgrade', - 'Sec-WebSocket-Origin: ' + (origin || 'null'), - 'Sec-WebSocket-Location: ws://' + this.request.headers.host + this.request.url - ]; - - if ('sec-websocket-protocol' in this.request.headers) { - headers.push('Sec-WebSocket-Protocol: ' + this.request.headers['sec-websocket-protocol']); - } - } - else { - - headers = [ - 'HTTP/1.1 101 Web Socket Protocol Handshake', - 'Upgrade: WebSocket', - 'Connection: Upgrade', - 'WebSocket-Origin: ' + this.request.headers.origin, - 'WebSocket-Location: ws://' + this.request.headers.host + this.request.url - ]; - - try { - this.connection.write(headers.concat('', '').join('\r\n')); - } catch(e){ - this._onClose(); - } - } + headers = [ + 'HTTP/1.1 101 WebSocket Protocol Handshake', + 'Upgrade: WebSocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Origin: ' + (origin || 'null'), + 'Sec-WebSocket-Location: ws://' + this.request.headers.host + this.request.url + ]; - this.connection.addListener('end', function(){self._onClose();}); - this.connection.addListener('data', function(data){self._handle(data);}); - - if (this._proveReception(headers)) this._payload(); - }, - - _handle: function(data){ - var chunk, chunks, chunk_count; - this.data += data; - chunks = this.data.split('\ufffd'); - chunk_count = chunks.length - 1; - for (var i = 0; i < chunk_count; i++) { - chunk = chunks[i]; - if (chunk[0] !== '\u0000') { - this.listener.options.log('Data incorrectly framed by UA. Dropping connection'); - this.connection.destroy(); - return false; - } - this._onMessage(chunk.slice(1)); + if ('sec-websocket-protocol' in this.request.headers){ + headers.push('Sec-WebSocket-Protocol: ' + this.request.headers['sec-websocket-protocol']); } - this.data = chunks[chunks.length - 1]; - }, - - // http://www.whatwg.org/specs/web-apps/current-work/complete/network.html#opening-handshake - _proveReception: function(headers){ - var k1 = this.request.headers['sec-websocket-key1'], - k2 = this.request.headers['sec-websocket-key2']; + } else { - if (k1 && k2) { - var md5 = crypto.createHash('md5'); - - [k1, k2].forEach(function(k) { - var n = parseInt(k.replace(/[^\d]/g, '')), - spaces = k.replace(/[^ ]/g, '').length; - - if (spaces === 0 || n % spaces !== 0) { - this.listener.options.log('Invalid WebSocket key: "' + k + '". Dropping connection'); - this.connection.destroy(); - return false; - } - - n /= spaces; - - md5.update(String.fromCharCode( - n >> 24 & 0xFF, - n >> 16 & 0xFF, - n >> 8 & 0xFF, - n & 0xFF)); - }); - - md5.update(this.upgradeHead.toString('binary')); - - try { - this.connection.write(headers.concat('', '').join('\r\n') + md5.digest('binary'), 'binary'); - } catch(e){ - this._onClose(); - } - } + headers = [ + 'HTTP/1.1 101 Web Socket Protocol Handshake', + 'Upgrade: WebSocket', + 'Connection: Upgrade', + 'WebSocket-Origin: ' + this.request.headers.origin, + 'WebSocket-Location: ws://' + this.request.headers.host + this.request.url + ]; - return true; - }, - - _write: function(message){ try { - this.connection.write('\u0000', 'binary'); - this.connection.write(message, 'utf8'); - this.connection.write('\uffff', 'binary'); + this.connection.write(headers.concat('', '').join('\r\n')); } catch(e){ this._onClose(); } } + + this.connection.addListener('end', function(){ + self._onClose(); + }); + + this.connection.addListener('data', function(data){ + self._handle(data); + }); -}); + if (this._proveReception(headers)) this._payload(); +}; -exports.websocket.httpUpgrade = true; \ No newline at end of file +WebSocket.prototype._handle = function(data){ + var chunk, chunks, chunk_count; + this.data += data; + chunks = this.data.split('\ufffd'); + chunk_count = chunks.length - 1; + for (var i = 0; i < chunk_count; i++){ + chunk = chunks[i]; + if (chunk[0] !== '\u0000'){ + this.listener.options.log('Data incorrectly framed by UA. Dropping connection'); + this.connection.destroy(); + return false; + } + this._onMessage(chunk.slice(1)); + } + this.data = chunks[chunks.length - 1]; +}; + + // http://www.whatwg.org/specs/web-apps/current-work/complete/network.html#opening-handshake +WebSocket.prototype._proveReception = function(headers){ + var k1 = this.request.headers['sec-websocket-key1'], + k2 = this.request.headers['sec-websocket-key2']; + + if (k1 && k2){ + var md5 = crypto.createHash('md5'); + + [k1, k2].forEach(function(k){ + var n = parseInt(k.replace(/[^\d]/g, '')), + spaces = k.replace(/[^ ]/g, '').length; + + if (spaces === 0 || n % spaces !== 0){ + this.listener.options.log('Invalid WebSocket key: "' + k + '". Dropping connection'); + this.connection.destroy(); + return false; + } + + n /= spaces; + + md5.update(String.fromCharCode( + n >> 24 & 0xFF, + n >> 16 & 0xFF, + n >> 8 & 0xFF, + n & 0xFF)); + }); + + md5.update(this.upgradeHead.toString('binary')); + + try { + this.connection.write(headers.concat('', '').join('\r\n') + md5.digest('binary'), 'binary'); + } catch(e){ + this._onClose(); + } + } + + return true; +}; + +WebSocket.prototype._write = function(message){ + try { + this.connection.write('\u0000', 'binary'); + this.connection.write(message, 'utf8'); + this.connection.write('\uffff', 'binary'); + } catch(e){ + this._onClose(); + } +}; + +WebSocket.httpUpgrade = true; \ No newline at end of file diff --git a/lib/socket.io/transports/xhr-multipart.js b/lib/socket.io/transports/xhr-multipart.js index 24a9499e..9f614ead 100644 --- a/lib/socket.io/transports/xhr-multipart.js +++ b/lib/socket.io/transports/xhr-multipart.js @@ -1,66 +1,60 @@ -var Client = require('../client').Client, - qs = require('querystring'); +var Client = require('../client'), + qs = require('querystring'), -exports['xhr-multipart'] = Client.extend({ +Multipart = module.exports = function(){}; - options: { - pingInterval: 7000 - }, - - _pingInterval: null, +sys.inherits(HTMLFile, Client); - _onConnect: function(req, res){ - var self = this, body = '', headers = {}; - // https://developer.mozilla.org/En/HTTP_Access_Control - if (req.headers.origin && this._verifyOrigin(req.headers.origin)) { - headers['Access-Control-Allow-Origin'] = req.headers.origin; - headers['Access-Control-Allow-Credentials'] = 'true'; - } - if (typeof req.headers['access-control-request-method'] !== 'undefined') { - // CORS preflight message - headers['Access-Control-Allow-Methods'] = req.headers['access-control-request-method']; - res.writeHead(200, headers); - res.write('ok'); - res.end(); - return; - } - switch (req.method){ - case 'GET': - this.__super__(req, res); - headers['Content-Type'] = 'multipart/x-mixed-replace;boundary="socketio"'; - headers['Connection'] = 'keep-alive'; - this.request.connection.addListener('end', function(){ self._onClose(); }); - this.response.useChunkedEncodingByDefault = false; - this.response.shouldKeepAlive = true; - this.response.writeHead(200, headers); - this.response.write("--socketio\n"); - if ('flush' in this.response) this.response.flush(); - this._payload(); - break; - - case 'POST': - req.addListener('data', function(message){ - body += message.toString(); - }); - req.addListener('end', function(){ - try { - var msg = qs.parse(body); - self._onMessage(msg.data); - } catch(e){} - res.writeHead(200, headers); - res.write('ok'); - res.end(); - body = ''; - }); - break; - } - }, - - _write: function(message){ - this.response.write("Content-Type: text/plain" + (message.length === 1 && message.charCodeAt(0) === 6 ? "; charset=us-ascii" : "") + "\n\n"); - this.response.write(message + "\n"); - this.response.write("--socketio\n"); - if ('flush' in this.response) this.response.flush(); +Multipart.prototype._onConnect = function(req, res){ + var self = this, body = '', headers = {}; + // https://developer.mozilla.org/En/HTTP_Access_Control + if (req.headers.origin && this._verifyOrigin(req.headers.origin)){ + headers['Access-Control-Allow-Origin'] = req.headers.origin; + headers['Access-Control-Allow-Credentials'] = 'true'; } + if (typeof req.headers['access-control-request-method'] !== 'undefined'){ + // CORS preflight message + headers['Access-Control-Allow-Methods'] = req.headers['access-control-request-method']; + res.writeHead(200, headers); + res.write('ok'); + res.end(); + return; + } + switch (req.method){ + case 'GET': + Client.prototype._onConnect.apply(this, [req, res]); + headers['Content-Type'] = 'multipart/x-mixed-replace;boundary="socketio"'; + headers['Connection'] = 'keep-alive'; + this.request.connection.addListener('end', function(){ self._onClose(); }); + this.response.useChunkedEncodingByDefault = false; + this.response.shouldKeepAlive = true; + this.response.writeHead(200, headers); + this.response.write("--socketio\n"); + if ('flush' in this.response) this.response.flush(); + this._payload(); + break; + + case 'POST': + req.addListener('data', function(message){ + body += message.toString(); + }); + req.addListener('end', function(){ + try { + var msg = qs.parse(body); + self._onMessage(msg.data); + } catch(e){} + res.writeHead(200, headers); + res.write('ok'); + res.end(); + body = ''; + }); + break; + } +}; -}); \ No newline at end of file +Multipart.prototype._write = function(message){ + this.response.write("Content-Type: text/plain" + (message.length === 1 && message.charCodeAt(0) === 6 ? "; charset=us-ascii" : "") + "\n\n"); + this.response.write(message + "\n"); + this.response.write("--socketio\n"); + if ('flush' in this.response) this.response.flush(); +}; \ No newline at end of file diff --git a/lib/socket.io/transports/xhr-polling.js b/lib/socket.io/transports/xhr-polling.js index f1626df4..1e93ee46 100644 --- a/lib/socket.io/transports/xhr-polling.js +++ b/lib/socket.io/transports/xhr-polling.js @@ -1,57 +1,48 @@ -var Client = require('../client').Client, - qs = require('querystring'); +var Client = require('../client'), + qs = require('querystring'), -exports['xhr-polling'] = Client.extend({ - - options: { - closeTimeout: 5000, - duration: 20000 - }, - - _onConnect: function(req, res){ - var self = this, body = ''; - switch (req.method){ - case 'GET': - this.__super__(req, res); - this._closeTimeout = setTimeout(function(){ - self._write(''); - }, this.options.duration); - this._payload(); - break; - - case 'POST': - req.addListener('data', function(message){ - body += message; - }); - req.addListener('end', function(){ - try { - var msg = qs.parse(body); - self._onMessage(msg.data); - } catch(e){} - res.writeHead(200); - res.write('ok'); - res.end(); - }); - break; - } - }, - - _write: function(message){ - if (this._closeTimeout) { - clearTimeout(this._closeTimeout); - } - var headers = {'Content-Type': 'text/plain', 'Content-Length': message.length}; - // https://developer.mozilla.org/En/HTTP_Access_Control - if (this.request.headers.origin && this._verifyOrigin(this.request.headers.origin)) { - headers['Access-Control-Allow-Origin'] = this.request.headersorigin; - if (this.request.headers.cookie) { - headers['Access-Control-Allow-Credentials'] = 'true'; - } - } - this.response.writeHead(200, headers); - this.response.write(message); - this.response.end(); - this._onClose(); +Polling = module.exports = function(){}; + +sys.inherits(Polling, Client); + +Polling.prototype._onConnect = function(req, res){ + var self = this, body = ''; + switch (req.method){ + case 'GET': + this.__super__(req, res); + this._closeTimeout = setTimeout(function(){ + self._write(''); + }, this.options.duration); + this._payload(); + break; + + case 'POST': + req.addListener('data', function(message){ + body += message; + }); + req.addListener('end', function(){ + try { + var msg = qs.parse(body); + self._onMessage(msg.data); + } catch(e){} + res.writeHead(200); + res.write('ok'); + res.end(); + }); + break; } +}; -}); \ No newline at end of file +Polling.prototype._write = function(message){ + if (this._closeTimeout) clearTimeout(this._closeTimeout); + var headers = {'Content-Type': 'text/plain', 'Content-Length': message.length}; + // https://developer.mozilla.org/En/HTTP_Access_Control + if (this.request.headers.origin && this._verifyOrigin(this.request.headers.origin)){ + headers['Access-Control-Allow-Origin'] = this.request.headersorigin; + if (this.request.headers.cookie) headers['Access-Control-Allow-Credentials'] = 'true'; + } + this.response.writeHead(200, headers); + this.response.write(message); + this.response.end(); + this._onClose(); +}; \ No newline at end of file diff --git a/lib/socket.io/util/array.js b/lib/socket.io/util/array.js deleted file mode 100644 index 7e0de59f..00000000 --- a/lib/socket.io/util/array.js +++ /dev/null @@ -1,20 +0,0 @@ -// Based on Mixin.js from MooTools (MIT) -// Copyright (c) 2006-2009 Valerio Proietti, - -exports.flatten = function(arr){ - var array = []; - for (var i = 0, l = arr.length; i < l; i++){ - var item = arr[i]; - if (item !== null) { - array = array.concat(item instanceof Array ? array.flatten(item) : item); - } - } - return array; -}; - -exports.include = function(arr, item){ - if (arr.indexOf(item) === -1) { - arr.push(item); - } - return arr; -}; \ No newline at end of file diff --git a/lib/socket.io/util/object.js b/lib/socket.io/util/object.js deleted file mode 100644 index 4603aee0..00000000 --- a/lib/socket.io/util/object.js +++ /dev/null @@ -1,51 +0,0 @@ -// Based on Mixin.js from MooTools (MIT) -// Copyright (c) 2006-2009 Valerio Proietti, - -exports.clone = function(item) { - var cloned; - if (item instanceof Array){ - cloned = []; - for (var i = 0; i < item.length; i++) { - cloned[i] = exports.clone(item[i]); - } - return cloned; - } - - if (typeof item === 'object') { - cloned = {}; - for (var key in item) { - cloned[key] = exports.clone(item[key]); - } - return cloned; - } - - return item; -}; - -var mergeOne = function(source, key, current){ - if (current instanceof Array){ - source[key] = exports.clone(current); - } else if (typeof current === 'object'){ - if (typeof source[key] === 'object') { - exports.merge(source[key], current); - } else { - source[key] = exports.clone(current); - } - } else { - source[key] = current; - } - return source; -}; - -exports.merge = function(source, k, v){ - if (typeof k === 'string') { - return mergeOne(source, k, v); - } - for (var i = 1, l = arguments.length; i < l; i++){ - var object = arguments[i]; - for (var key in object) { - mergeOne(source, key, object[key]); - } - } - return source; -}; diff --git a/lib/socket.io/util/options.js b/lib/socket.io/util/options.js deleted file mode 100644 index 7d08b937..00000000 --- a/lib/socket.io/util/options.js +++ /dev/null @@ -1,35 +0,0 @@ -// Based on Mixin.js from MooTools (MIT) -// Copyright (c) 2006-2009 Valerio Proietti, -var object = require('./object'), sys = require('sys'); - -exports.options = { - - options: {}, - - setOption: function(key, value){ - object.merge(this.options, key, value); - return this; - }, - - setOptions: function(options){ - for (var key in options) { - this.setOption(key, options[key]); - } - if (this.addListener){ - var first_lower = function(full, first){ - return first.toLowerCase(); - }; - - // Automagically register callbacks if the varname starts with on - for (var i in this.options){ - if (!(/^on[A-Z]/).test(i) || typeof this.options[i] !== 'function') { - continue; - } - this.addListener(i.replace(/^on([A-Z])/, first_lower), this.options[i]); - this.options[i] = null; - } - } - return this; - } - -}; \ No newline at end of file diff --git a/lib/socket.io/utils.js b/lib/socket.io/utils.js new file mode 100644 index 00000000..9dd819a6 --- /dev/null +++ b/lib/socket.io/utils.js @@ -0,0 +1,12 @@ +var Options = exports.options = function(){}; + +Options.prototype = { + options: function(options, merge){ + this.options = exports.merge(options || {}, merge || {}); + } +}; + +exports.merge = function(source, merge){ + for (var i in merge) source[i] = merge[i]; + return source; +}; \ No newline at end of file diff --git a/lib/vendor/js-oo b/lib/vendor/js-oo deleted file mode 160000 index 1f94bd89..00000000 --- a/lib/vendor/js-oo +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1f94bd897994a952114cb1f280c1949d5e6a0648