123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 |
- /*!
- * socket.io-node
- * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
- * MIT Licensed
- */
-
- /**
- * Module requirements.
- */
- var Transport = require('../../transport')
- , EventEmitter = process.EventEmitter
- , crypto = require('crypto')
- , url = require('url')
- , parser = require('../../parser')
- , util = require('../../util');
- /**
- * Export the constructor.
- */
- exports = module.exports = WebSocket;
- exports.Parser = Parser;
- /**
- * HTTP interface constructor. Interface compatible with all transports that
- * depend on request-response cycles.
- *
- * @api public
- */
- function WebSocket (mng, data, req) {
- // parser
- var self = this;
- this.manager = mng;
- this.parser = new Parser();
- this.parser.on('data', function (packet) {
- self.onMessage(parser.decodePacket(packet));
- });
- this.parser.on('ping', function () {
- // version 8 ping => pong
- try {
- self.socket.write('\u008a\u0000');
- }
- catch (e) {
- self.end();
- return;
- }
- });
- this.parser.on('close', function () {
- self.end();
- });
- this.parser.on('error', function (reason) {
- self.log.warn(self.name + ' parser error: ' + reason);
- self.end();
- });
- Transport.call(this, mng, data, req);
- };
- /**
- * Inherits from Transport.
- */
- WebSocket.prototype.__proto__ = Transport.prototype;
- /**
- * Transport name
- *
- * @api public
- */
- WebSocket.prototype.name = 'websocket';
- /**
- * Websocket draft version
- *
- * @api public
- */
- WebSocket.prototype.protocolVersion = '07-12';
- /**
- * Called when the socket connects.
- *
- * @api private
- */
- WebSocket.prototype.onSocketConnect = function () {
- var self = this;
- if (typeof this.req.headers.upgrade === 'undefined' ||
- this.req.headers.upgrade.toLowerCase() !== 'websocket') {
- this.log.warn(this.name + ' connection invalid');
- this.end();
- return;
- }
- var origin = this.req.headers['sec-websocket-origin']
- , location = ((this.manager.settings['match origin protocol'] ?
- origin.match(/^https/) : this.socket.encrypted) ?
- 'wss' : 'ws')
- + '://' + this.req.headers.host + this.req.url;
-
- if (!this.verifyOrigin(origin)) {
- this.log.warn(this.name + ' connection invalid: origin mismatch');
- this.end();
- return;
- }
-
- if (!this.req.headers['sec-websocket-key']) {
- this.log.warn(this.name + ' connection invalid: received no key');
- this.end();
- return;
- }
-
- // calc key
- var key = this.req.headers['sec-websocket-key'];
- var shasum = crypto.createHash('sha1');
- shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
- key = shasum.digest('base64');
- var headers = [
- 'HTTP/1.1 101 Switching Protocols'
- , 'Upgrade: websocket'
- , 'Connection: Upgrade'
- , 'Sec-WebSocket-Accept: ' + key
- ];
- try {
- this.socket.write(headers.concat('', '').join('\r\n'));
- this.socket.setTimeout(0);
- this.socket.setNoDelay(true);
- } catch (e) {
- this.end();
- return;
- }
- this.socket.on('data', function (data) {
- self.parser.add(data);
- });
- };
- /**
- * Verifies the origin of a request.
- *
- * @api private
- */
- WebSocket.prototype.verifyOrigin = function (origin) {
- var origins = this.manager.get('origins');
- if (origin === 'null') origin = '*';
- if (origins.indexOf('*:*') !== -1) {
- return true;
- }
- if (origin) {
- try {
- var parts = url.parse(origin);
- parts.port = parts.port || 80;
- var ok =
- ~origins.indexOf(parts.hostname + ':' + parts.port) ||
- ~origins.indexOf(parts.hostname + ':*') ||
- ~origins.indexOf('*:' + parts.port);
- if (!ok) this.log.warn('illegal origin: ' + origin);
- return ok;
- } catch (ex) {
- this.log.warn('error parsing origin');
- }
- }
- else {
- this.log.warn('origin missing from websocket call, yet required by config');
- }
- return false;
- };
- /**
- * Writes to the socket.
- *
- * @api private
- */
- WebSocket.prototype.write = function (data) {
- if (this.open) {
- var buf = this.frame(0x81, data);
- try {
- this.socket.write(buf, 'binary');
- }
- catch (e) {
- this.end();
- return;
- }
- this.log.debug(this.name + ' writing', data);
- }
- };
- /**
- * Writes a payload.
- *
- * @api private
- */
- WebSocket.prototype.payload = function (msgs) {
- for (var i = 0, l = msgs.length; i < l; i++) {
- this.write(msgs[i]);
- }
- return this;
- };
- /**
- * Frame server-to-client output as a text packet.
- *
- * @api private
- */
- WebSocket.prototype.frame = function (opcode, str) {
- var dataBuffer = new Buffer(str)
- , dataLength = dataBuffer.length
- , startOffset = 2
- , secondByte = dataLength;
- if (dataLength > 65536) {
- startOffset = 10;
- secondByte = 127;
- }
- else if (dataLength > 125) {
- startOffset = 4;
- secondByte = 126;
- }
- var outputBuffer = new Buffer(dataLength + startOffset);
- outputBuffer[0] = opcode;
- outputBuffer[1] = secondByte;
- dataBuffer.copy(outputBuffer, startOffset);
- switch (secondByte) {
- case 126:
- outputBuffer[2] = dataLength >>> 8;
- outputBuffer[3] = dataLength % 256;
- break;
- case 127:
- var l = dataLength;
- for (var i = 1; i <= 8; ++i) {
- outputBuffer[startOffset - i] = l & 0xff;
- l >>>= 8;
- }
- }
- return outputBuffer;
- };
- /**
- * Closes the connection.
- *
- * @api private
- */
- WebSocket.prototype.doClose = function () {
- this.socket.end();
- };
- /**
- * WebSocket parser
- *
- * @api public
- */
-
- function Parser () {
- this.state = {
- activeFragmentedOperation: null,
- lastFragment: false,
- masked: false,
- opcode: 0
- };
- this.overflow = null;
- this.expectOffset = 0;
- this.expectBuffer = null;
- this.expectHandler = null;
- this.currentMessage = '';
- var self = this;
- this.opcodeHandlers = {
- // text
- '1': function(data) {
- var finish = function(mask, data) {
- self.currentMessage += self.unmask(mask, data);
- if (self.state.lastFragment) {
- self.emit('data', self.currentMessage);
- self.currentMessage = '';
- }
- self.endPacket();
- }
- var expectData = function(length) {
- if (self.state.masked) {
- self.expect('Mask', 4, function(data) {
- var mask = data;
- self.expect('Data', length, function(data) {
- finish(mask, data);
- });
- });
- }
- else {
- self.expect('Data', length, function(data) {
- finish(null, data);
- });
- }
- }
- // decode length
- var firstLength = data[1] & 0x7f;
- if (firstLength < 126) {
- expectData(firstLength);
- }
- else if (firstLength == 126) {
- self.expect('Length', 2, function(data) {
- expectData(util.unpack(data));
- });
- }
- else if (firstLength == 127) {
- self.expect('Length', 8, function(data) {
- if (util.unpack(data.slice(0, 4)) != 0) {
- self.error('packets with length spanning more than 32 bit is currently not supported');
- return;
- }
- var lengthBytes = data.slice(4); // note: cap to 32 bit length
- expectData(util.unpack(data));
- });
- }
- },
- // binary
- '2': function(data) {
- var finish = function(mask, data) {
- if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list
- self.currentMessage.push(self.unmask(mask, data, true));
- if (self.state.lastFragment) {
- self.emit('binary', self.concatBuffers(self.currentMessage));
- self.currentMessage = '';
- }
- self.endPacket();
- }
- var expectData = function(length) {
- if (self.state.masked) {
- self.expect('Mask', 4, function(data) {
- var mask = data;
- self.expect('Data', length, function(data) {
- finish(mask, data);
- });
- });
- }
- else {
- self.expect('Data', length, function(data) {
- finish(null, data);
- });
- }
- }
- // decode length
- var firstLength = data[1] & 0x7f;
- if (firstLength < 126) {
- expectData(firstLength);
- }
- else if (firstLength == 126) {
- self.expect('Length', 2, function(data) {
- expectData(util.unpack(data));
- });
- }
- else if (firstLength == 127) {
- self.expect('Length', 8, function(data) {
- if (util.unpack(data.slice(0, 4)) != 0) {
- self.error('packets with length spanning more than 32 bit is currently not supported');
- return;
- }
- var lengthBytes = data.slice(4); // note: cap to 32 bit length
- expectData(util.unpack(data));
- });
- }
- },
- // close
- '8': function(data) {
- self.emit('close');
- self.reset();
- },
- // ping
- '9': function(data) {
- if (self.state.lastFragment == false) {
- self.error('fragmented ping is not supported');
- return;
- }
-
- var finish = function(mask, data) {
- self.emit('ping', self.unmask(mask, data));
- self.endPacket();
- }
- var expectData = function(length) {
- if (self.state.masked) {
- self.expect('Mask', 4, function(data) {
- var mask = data;
- self.expect('Data', length, function(data) {
- finish(mask, data);
- });
- });
- }
- else {
- self.expect('Data', length, function(data) {
- finish(null, data);
- });
- }
- }
- // decode length
- var firstLength = data[1] & 0x7f;
- if (firstLength == 0) {
- finish(null, null);
- }
- else if (firstLength < 126) {
- expectData(firstLength);
- }
- else if (firstLength == 126) {
- self.expect('Length', 2, function(data) {
- expectData(util.unpack(data));
- });
- }
- else if (firstLength == 127) {
- self.expect('Length', 8, function(data) {
- expectData(util.unpack(data));
- });
- }
- }
- }
- this.expect('Opcode', 2, this.processPacket);
- };
- /**
- * Inherits from EventEmitter.
- */
- Parser.prototype.__proto__ = EventEmitter.prototype;
- /**
- * Add new data to the parser.
- *
- * @api public
- */
- Parser.prototype.add = function(data) {
- if (this.expectBuffer == null) {
- this.addToOverflow(data);
- return;
- }
- var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset);
- data.copy(this.expectBuffer, this.expectOffset, 0, toRead);
- this.expectOffset += toRead;
- if (toRead < data.length) {
- // at this point the overflow buffer shouldn't at all exist
- this.overflow = new Buffer(data.length - toRead);
- data.copy(this.overflow, 0, toRead, toRead + this.overflow.length);
- }
- if (this.expectOffset == this.expectBuffer.length) {
- var bufferForHandler = this.expectBuffer;
- this.expectBuffer = null;
- this.expectOffset = 0;
- this.expectHandler.call(this, bufferForHandler);
- }
- }
- /**
- * Adds a piece of data to the overflow.
- *
- * @api private
- */
- Parser.prototype.addToOverflow = function(data) {
- if (this.overflow == null) this.overflow = data;
- else {
- var prevOverflow = this.overflow;
- this.overflow = new Buffer(this.overflow.length + data.length);
- prevOverflow.copy(this.overflow, 0);
- data.copy(this.overflow, prevOverflow.length);
- }
- }
- /**
- * Waits for a certain amount of bytes to be available, then fires a callback.
- *
- * @api private
- */
- Parser.prototype.expect = function(what, length, handler) {
- this.expectBuffer = new Buffer(length);
- this.expectOffset = 0;
- this.expectHandler = handler;
- if (this.overflow != null) {
- var toOverflow = this.overflow;
- this.overflow = null;
- this.add(toOverflow);
- }
- }
- /**
- * Start processing a new packet.
- *
- * @api private
- */
- Parser.prototype.processPacket = function (data) {
- if ((data[0] & 0x70) != 0) {
- this.error('reserved fields must be empty');
- }
- this.state.lastFragment = (data[0] & 0x80) == 0x80;
- this.state.masked = (data[1] & 0x80) == 0x80;
- var opcode = data[0] & 0xf;
- if (opcode == 0) {
- // continuation frame
- this.state.opcode = this.state.activeFragmentedOperation;
- if (!(this.state.opcode == 1 || this.state.opcode == 2)) {
- this.error('continuation frame cannot follow current opcode')
- return;
- }
- }
- else {
- this.state.opcode = opcode;
- if (this.state.lastFragment === false) {
- this.state.activeFragmentedOperation = opcode;
- }
- }
- var handler = this.opcodeHandlers[this.state.opcode];
- if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode);
- else handler(data);
- }
- /**
- * Endprocessing a packet.
- *
- * @api private
- */
- Parser.prototype.endPacket = function() {
- this.expectOffset = 0;
- this.expectBuffer = null;
- this.expectHandler = null;
- if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) {
- // end current fragmented operation
- this.state.activeFragmentedOperation = null;
- }
- this.state.lastFragment = false;
- this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
- this.state.masked = false;
- this.expect('Opcode', 2, this.processPacket);
- }
- /**
- * Reset the parser state.
- *
- * @api private
- */
- Parser.prototype.reset = function() {
- this.state = {
- activeFragmentedOperation: null,
- lastFragment: false,
- masked: false,
- opcode: 0
- };
- this.expectOffset = 0;
- this.expectBuffer = null;
- this.expectHandler = null;
- this.overflow = null;
- this.currentMessage = '';
- }
- /**
- * Unmask received data.
- *
- * @api private
- */
- Parser.prototype.unmask = function (mask, buf, binary) {
- if (mask != null) {
- for (var i = 0, ll = buf.length; i < ll; i++) {
- buf[i] ^= mask[i % 4];
- }
- }
- if (binary) return buf;
- return buf != null ? buf.toString('utf8') : '';
- }
- /**
- * Concatenates a list of buffers.
- *
- * @api private
- */
- Parser.prototype.concatBuffers = function(buffers) {
- var length = 0;
- for (var i = 0, l = buffers.length; i < l; ++i) {
- length += buffers[i].length;
- }
- var mergedBuffer = new Buffer(length);
- var offset = 0;
- for (var i = 0, l = buffers.length; i < l; ++i) {
- buffers[i].copy(mergedBuffer, offset);
- offset += buffers[i].length;
- }
- return mergedBuffer;
- }
- /**
- * Handles an error
- *
- * @api private
- */
- Parser.prototype.error = function (reason) {
- this.reset();
- this.emit('error', reason);
- return this;
- };
|