/js/lib/Socket.IO-node/lib/socket.io/transports/websocket/index.js
JavaScript | 203 lines | 125 code | 37 blank | 41 comment | 18 complexity | fc87814577db5783fdbb0589aa7f9f0f MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
- /*!
- * Socket.IO - transports - WebSocket
- * Copyright (c) 2010-2011 Guillermo Rauch <guillermo@learnboost.com>
- * MIT Licensed
- */
- var Client = require('../../client')
- , Stream = require('net').Stream
- , EventEmitter = require('events').EventEmitter
- , Parser = require('./parser')
- , crypto = require('crypto')
- , url = require('url');
- /**
- * Expose `WebSocket`.
- */
- module.exports = WebSocket;
- /**
- * Initialize a `WebSocket` client.
- *
- * @api private
- */
- function WebSocket(){
- Client.apply(this, arguments);
- };
- /**
- * Inherit from `Client.prototype`.
- */
- WebSocket.prototype.__proto__ = Client.prototype;
- WebSocket.prototype._onConnect = function(req, socket){
- var self = this
- , headers = [];
-
- if (!req.connection.setTimeout){
- req.connection.end();
- return false;
- }
- this.parser = new Parser();
- this.parser.on('data', self._onMessage.bind(this));
- this.parser.on('error', self._onClose.bind(this));
- Client.prototype._onConnect.call(this, req);
-
- if (this.request.headers.upgrade !== 'WebSocket' || !this._verifyOrigin(this.request.headers.origin)){
- this.listener.log('WebSocket connection invalid or Origin not verified');
- this._onClose();
- return false;
- }
-
- var origin = this.request.headers.origin,
- location = (this.request.socket.encrypted ? 'wss' : 'ws')
- + '://' + this.request.headers.host + this.request.url;
-
- this.waitingForNonce = false;
- if ('sec-websocket-key1' in this.request.headers){
- /* We need to send the 101 response immediately when using Draft 76 with
- a load balancing proxy, such as HAProxy. In order to protect an
- unsuspecting non-websocket HTTP server, HAProxy will not send the
- 8-byte nonce through the connection until the Upgrade: WebSocket
- request has been confirmed by the WebSocket server by a 101 response
- indicating that the server can handle the upgraded protocol. We
- therefore must send the 101 response immediately, and then wait for
- the nonce to be forwarded to us afterward in order to finish the
- Draft 76 handshake.
- */
-
- // If we don't have the nonce yet, wait for it.
- if (!(this.upgradeHead && this.upgradeHead.length >= 8)) {
- this.waitingForNonce = true;
- }
-
- headers = [
- 'HTTP/1.1 101 WebSocket Protocol Handshake'
- , 'Upgrade: WebSocket'
- , 'Connection: Upgrade'
- , 'Sec-WebSocket-Origin: ' + origin
- , 'Sec-WebSocket-Location: ' + location
- ];
-
- 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: ' + origin
- , 'WebSocket-Location: ' + location
- ];
- }
- try {
- this.connection.write(headers.concat('', '').join('\r\n'));
- this.connection.setTimeout(0);
- this.connection.setNoDelay(true);
- this.connection.setEncoding('utf-8');
- } catch(e){
- this._onClose();
- return;
- }
-
- if (this.waitingForNonce) {
- // Since we will be receiving the binary nonce through the normal HTTP
- // data event, set the connection to 'binary' temporarily
- this.connection.setEncoding('binary');
- this._headers = headers;
- }
- else {
- if (this._proveReception(headers)) this._payload();
- }
-
- this.buffer = '';
-
- this.connection.on('data', function(data){
- if (self.waitingForNonce) {
- self.buffer += data;
- if (self.buffer.length < 8) return;
- // Restore the connection to utf8 encoding after receiving the nonce
- self.connection.setEncoding('utf8');
- self.waitingForNonce = false;
- // Stuff the nonce into the location where it's expected to be
- self.upgradeHead = self.buffer.substr(0,8);
- self.buffer = '';
- if (self._proveReception(self._headers)) self._payload();
- return;
- }
- self.parser.write(data);
- });
- };
- // http://www.whatwg.org/specs/web-apps/current-work/complete/network.html#opening-handshake
- WebSocket.prototype._proveReception = function(headers){
- var self = this
- , 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){
- self.listener.log('Invalid WebSocket key: "' + k + '". Dropping connection');
- self._onClose();
- 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(md5.digest('binary'), 'binary');
- } catch(e){
- this._onClose();
- }
- }
-
- return true;
- };
- /**
- * Write implementation.
- *
- * @param {String} message
- * @api private
- */
- 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();
- }
- };
- /**
- * Requires upgrade.
- */
- WebSocket.httpUpgrade = true;