aboutsummaryrefslogtreecommitdiffstats
path: root/signaling-server/node_modules/socket.io/node_modules/socket.io-client/node_modules/ws/lib/WebSocketServer.js
diff options
context:
space:
mode:
Diffstat (limited to 'signaling-server/node_modules/socket.io/node_modules/socket.io-client/node_modules/ws/lib/WebSocketServer.js')
-rw-r--r--signaling-server/node_modules/socket.io/node_modules/socket.io-client/node_modules/ws/lib/WebSocketServer.js465
1 files changed, 465 insertions, 0 deletions
diff --git a/signaling-server/node_modules/socket.io/node_modules/socket.io-client/node_modules/ws/lib/WebSocketServer.js b/signaling-server/node_modules/socket.io/node_modules/socket.io-client/node_modules/ws/lib/WebSocketServer.js
new file mode 100644
index 0000000..5cbd195
--- /dev/null
+++ b/signaling-server/node_modules/socket.io/node_modules/socket.io-client/node_modules/ws/lib/WebSocketServer.js
@@ -0,0 +1,465 @@
+/*!
+ * ws: a node.js websocket client
+ * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
+ * MIT Licensed
+ */
+
+var util = require('util')
+ , events = require('events')
+ , http = require('http')
+ , crypto = require('crypto')
+ , Options = require('options')
+ , WebSocket = require('./WebSocket')
+ , tls = require('tls')
+ , url = require('url');
+
+/**
+ * WebSocket Server implementation
+ */
+
+function WebSocketServer(options, callback) {
+ options = new Options({
+ host: '0.0.0.0',
+ port: null,
+ server: null,
+ verifyClient: null,
+ handleProtocols: null,
+ path: null,
+ noServer: false,
+ disableHixie: false,
+ clientTracking: true
+ }).merge(options);
+
+ if (!options.isDefinedAndNonNull('port') && !options.isDefinedAndNonNull('server') && !options.value.noServer) {
+ throw new TypeError('`port` or a `server` must be provided');
+ }
+
+ var self = this;
+
+ if (options.isDefinedAndNonNull('port')) {
+ this._server = http.createServer(function (req, res) {
+ res.writeHead(200, {'Content-Type': 'text/plain'});
+ res.end('Not implemented');
+ });
+ this._server.listen(options.value.port, options.value.host, callback);
+ this._closeServer = function() { if (self._server) self._server.close(); };
+ }
+ else if (options.value.server) {
+ this._server = options.value.server;
+ if (options.value.path) {
+ // take note of the path, to avoid collisions when multiple websocket servers are
+ // listening on the same http server
+ if (this._server._webSocketPaths && options.value.server._webSocketPaths[options.value.path]) {
+ throw new Error('two instances of WebSocketServer cannot listen on the same http server path');
+ }
+ if (typeof this._server._webSocketPaths !== 'object') {
+ this._server._webSocketPaths = {};
+ }
+ this._server._webSocketPaths[options.value.path] = 1;
+ }
+ }
+ if (this._server) this._server.once('listening', function() { self.emit('listening'); });
+
+ if (typeof this._server != 'undefined') {
+ this._server.on('error', function(error) {
+ self.emit('error', error)
+ });
+ this._server.on('upgrade', function(req, socket, upgradeHead) {
+ //copy upgradeHead to avoid retention of large slab buffers used in node core
+ var head = new Buffer(upgradeHead.length);
+ upgradeHead.copy(head);
+
+ self.handleUpgrade(req, socket, head, function(client) {
+ self.emit('connection'+req.url, client);
+ self.emit('connection', client);
+ });
+ });
+ }
+
+ this.options = options.value;
+ this.path = options.value.path;
+ this.clients = [];
+}
+
+/**
+ * Inherits from EventEmitter.
+ */
+
+util.inherits(WebSocketServer, events.EventEmitter);
+
+/**
+ * Immediately shuts down the connection.
+ *
+ * @api public
+ */
+
+WebSocketServer.prototype.close = function() {
+ // terminate all associated clients
+ var error = null;
+ try {
+ for (var i = 0, l = this.clients.length; i < l; ++i) {
+ this.clients[i].terminate();
+ }
+ }
+ catch (e) {
+ error = e;
+ }
+
+ // remove path descriptor, if any
+ if (this.path && this._server._webSocketPaths) {
+ delete this._server._webSocketPaths[this.path];
+ if (Object.keys(this._server._webSocketPaths).length == 0) {
+ delete this._server._webSocketPaths;
+ }
+ }
+
+ // close the http server if it was internally created
+ try {
+ if (typeof this._closeServer !== 'undefined') {
+ this._closeServer();
+ }
+ }
+ finally {
+ delete this._server;
+ }
+ if (error) throw error;
+}
+
+/**
+ * Handle a HTTP Upgrade request.
+ *
+ * @api public
+ */
+
+WebSocketServer.prototype.handleUpgrade = function(req, socket, upgradeHead, cb) {
+ // check for wrong path
+ if (this.options.path) {
+ var u = url.parse(req.url);
+ if (u && u.pathname !== this.options.path) return;
+ }
+
+ if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') {
+ abortConnection(socket, 400, 'Bad Request');
+ return;
+ }
+
+ if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments);
+ else handleHybiUpgrade.apply(this, arguments);
+}
+
+module.exports = WebSocketServer;
+
+/**
+ * Entirely private apis,
+ * which may or may not be bound to a sepcific WebSocket instance.
+ */
+
+function handleHybiUpgrade(req, socket, upgradeHead, cb) {
+ // handle premature socket errors
+ var errorHandler = function() {
+ try { socket.destroy(); } catch (e) {}
+ }
+ socket.on('error', errorHandler);
+
+ // verify key presence
+ if (!req.headers['sec-websocket-key']) {
+ abortConnection(socket, 400, 'Bad Request');
+ return;
+ }
+
+ // verify version
+ var version = parseInt(req.headers['sec-websocket-version']);
+ if ([8, 13].indexOf(version) === -1) {
+ abortConnection(socket, 400, 'Bad Request');
+ return;
+ }
+
+ // verify protocol
+ var protocols = req.headers['sec-websocket-protocol'];
+
+ // verify client
+ var origin = version < 13 ?
+ req.headers['sec-websocket-origin'] :
+ req.headers['origin'];
+
+ // handler to call when the connection sequence completes
+ var self = this;
+ var completeHybiUpgrade2 = function(protocol) {
+
+ // calc key
+ var key = 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
+ ];
+
+ if (typeof protocol != 'undefined') {
+ headers.push('Sec-WebSocket-Protocol: ' + protocol);
+ }
+
+ // allows external modification/inspection of handshake headers
+ self.emit('headers', headers);
+
+ socket.setTimeout(0);
+ socket.setNoDelay(true);
+ try {
+ socket.write(headers.concat('', '').join('\r\n'));
+ }
+ catch (e) {
+ // if the upgrade write fails, shut the connection down hard
+ try { socket.destroy(); } catch (e) {}
+ return;
+ }
+
+ var client = new WebSocket([req, socket, upgradeHead], {
+ protocolVersion: version,
+ protocol: protocol
+ });
+
+ if (self.options.clientTracking) {
+ self.clients.push(client);
+ client.on('close', function() {
+ var index = self.clients.indexOf(client);
+ if (index != -1) {
+ self.clients.splice(index, 1);
+ }
+ });
+ }
+
+ // signal upgrade complete
+ socket.removeListener('error', errorHandler);
+ cb(client);
+ }
+
+ // optionally call external protocol selection handler before
+ // calling completeHybiUpgrade2
+ var completeHybiUpgrade1 = function() {
+ // choose from the sub-protocols
+ if (typeof self.options.handleProtocols == 'function') {
+ var protList = (protocols || "").split(/, */);
+ var callbackCalled = false;
+ var res = self.options.handleProtocols(protList, function(result, protocol) {
+ callbackCalled = true;
+ if (!result) abortConnection(socket, 404, 'Unauthorized')
+ else completeHybiUpgrade2(protocol);
+ });
+ if (!callbackCalled) {
+ // the handleProtocols handler never called our callback
+ abortConnection(socket, 501, 'Could not process protocols');
+ }
+ return;
+ } else {
+ if (typeof protocols !== 'undefined') {
+ completeHybiUpgrade2(protocols.split(/, */)[0]);
+ }
+ else {
+ completeHybiUpgrade2();
+ }
+ }
+ }
+
+ // optionally call external client verification handler
+ if (typeof this.options.verifyClient == 'function') {
+ var info = {
+ origin: origin,
+ secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined',
+ req: req
+ };
+ if (this.options.verifyClient.length == 2) {
+ this.options.verifyClient(info, function(result, code, name) {
+ if (typeof code === 'undefined') code = 401;
+ if (typeof name === 'undefined') name = http.STATUS_CODES[code];
+
+ if (!result) abortConnection(socket, code, name);
+ else completeHybiUpgrade1();
+ });
+ return;
+ }
+ else if (!this.options.verifyClient(info)) {
+ abortConnection(socket, 401, 'Unauthorized');
+ return;
+ }
+ }
+
+ completeHybiUpgrade1();
+}
+
+function handleHixieUpgrade(req, socket, upgradeHead, cb) {
+ // handle premature socket errors
+ var errorHandler = function() {
+ try { socket.destroy(); } catch (e) {}
+ }
+ socket.on('error', errorHandler);
+
+ // bail if options prevent hixie
+ if (this.options.disableHixie) {
+ abortConnection(socket, 401, 'Hixie support disabled');
+ return;
+ }
+
+ // verify key presence
+ if (!req.headers['sec-websocket-key2']) {
+ abortConnection(socket, 400, 'Bad Request');
+ return;
+ }
+
+ var origin = req.headers['origin']
+ , self = this;
+
+ // setup handshake completion to run after client has been verified
+ var onClientVerified = function() {
+ var wshost;
+ if (!req.headers['x-forwarded-host'])
+ wshost = req.headers.host;
+ else
+ wshost = req.headers['x-forwarded-host'];
+ var location = ((req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws') + '://' + wshost + req.url
+ , protocol = req.headers['sec-websocket-protocol'];
+
+ // handshake completion code to run once nonce has been successfully retrieved
+ var completeHandshake = function(nonce, rest) {
+ // calculate key
+ var k1 = req.headers['sec-websocket-key1']
+ , k2 = req.headers['sec-websocket-key2']
+ , 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){
+ abortConnection(socket, 400, 'Bad Request');
+ return;
+ }
+ n /= spaces;
+ md5.update(String.fromCharCode(
+ n >> 24 & 0xFF,
+ n >> 16 & 0xFF,
+ n >> 8 & 0xFF,
+ n & 0xFF));
+ });
+ md5.update(nonce.toString('binary'));
+
+ var headers = [
+ 'HTTP/1.1 101 Switching Protocols'
+ , 'Upgrade: WebSocket'
+ , 'Connection: Upgrade'
+ , 'Sec-WebSocket-Location: ' + location
+ ];
+ if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol);
+ if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin);
+
+ socket.setTimeout(0);
+ socket.setNoDelay(true);
+ try {
+ // merge header and hash buffer
+ var headerBuffer = new Buffer(headers.concat('', '').join('\r\n'));
+ var hashBuffer = new Buffer(md5.digest('binary'), 'binary');
+ var handshakeBuffer = new Buffer(headerBuffer.length + hashBuffer.length);
+ headerBuffer.copy(handshakeBuffer, 0);
+ hashBuffer.copy(handshakeBuffer, headerBuffer.length);
+
+ // do a single write, which - upon success - causes a new client websocket to be setup
+ socket.write(handshakeBuffer, 'binary', function(err) {
+ if (err) return; // do not create client if an error happens
+ var client = new WebSocket([req, socket, rest], {
+ protocolVersion: 'hixie-76',
+ protocol: protocol
+ });
+ if (self.options.clientTracking) {
+ self.clients.push(client);
+ client.on('close', function() {
+ var index = self.clients.indexOf(client);
+ if (index != -1) {
+ self.clients.splice(index, 1);
+ }
+ });
+ }
+
+ // signal upgrade complete
+ socket.removeListener('error', errorHandler);
+ cb(client);
+ });
+ }
+ catch (e) {
+ try { socket.destroy(); } catch (e) {}
+ return;
+ }
+ }
+
+ // retrieve nonce
+ var nonceLength = 8;
+ if (upgradeHead && upgradeHead.length >= nonceLength) {
+ var nonce = upgradeHead.slice(0, nonceLength);
+ var rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null;
+ completeHandshake.call(self, nonce, rest);
+ }
+ else {
+ // nonce not present in upgradeHead, so we must wait for enough data
+ // data to arrive before continuing
+ var nonce = new Buffer(nonceLength);
+ upgradeHead.copy(nonce, 0);
+ var received = upgradeHead.length;
+ var rest = null;
+ var handler = function (data) {
+ var toRead = Math.min(data.length, nonceLength - received);
+ if (toRead === 0) return;
+ data.copy(nonce, received, 0, toRead);
+ received += toRead;
+ if (received == nonceLength) {
+ socket.removeListener('data', handler);
+ if (toRead < data.length) rest = data.slice(toRead);
+ completeHandshake.call(self, nonce, rest);
+ }
+ }
+ socket.on('data', handler);
+ }
+ }
+
+ // verify client
+ if (typeof this.options.verifyClient == 'function') {
+ var info = {
+ origin: origin,
+ secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined',
+ req: req
+ };
+ if (this.options.verifyClient.length == 2) {
+ var self = this;
+ this.options.verifyClient(info, function(result, code, name) {
+ if (typeof code === 'undefined') code = 401;
+ if (typeof name === 'undefined') name = http.STATUS_CODES[code];
+
+ if (!result) abortConnection(socket, code, name);
+ else onClientVerified.apply(self);
+ });
+ return;
+ }
+ else if (!this.options.verifyClient(info)) {
+ abortConnection(socket, 401, 'Unauthorized');
+ return;
+ }
+ }
+
+ // no client verification required
+ onClientVerified();
+}
+
+function abortConnection(socket, code, name) {
+ try {
+ var response = [
+ 'HTTP/1.1 ' + code + ' ' + name,
+ 'Content-type: text/html'
+ ];
+ socket.write(response.concat('', '').join('\r\n'));
+ }
+ catch (e) { /* ignore errors - we've aborted this connection */ }
+ finally {
+ // ensure that an early aborted connection is shut down completely
+ try { socket.destroy(); } catch (e) {}
+ }
+}