From f4c1f850a0263fa2ba439e21a2b5cc93df92a6c8 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 21 Oct 2016 09:58:24 +0200 Subject: [PATCH 1/2] [major] Drop support for Hixie-76 --- README.md | 3 - doc/ws.md | 5 +- lib/Receiver.hixie.js | 170 ---------------- lib/Sender.hixie.js | 112 ----------- lib/WebSocket.js | 20 +- lib/WebSocketServer.js | 193 +----------------- package.json | 2 - test/Receiver.hixie.test.js | 170 ---------------- test/Sender.hixie.test.js | 146 -------------- test/WebSocket.test.js | 25 +-- test/WebSocketServer.test.js | 379 +++-------------------------------- 11 files changed, 47 insertions(+), 1178 deletions(-) delete mode 100644 lib/Receiver.hixie.js delete mode 100644 lib/Sender.hixie.js delete mode 100644 test/Receiver.hixie.test.js delete mode 100644 test/Sender.hixie.test.js diff --git a/README.md b/README.md index 93106d7a3..e7c531195 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,6 @@ for the full reports. ## Protocol support -* **Hixie draft 76** (Old and deprecated, but still in use by Safari and Opera. - Added to ws version 0.4.2, but server only. Can be disabled by setting the - `disableHixie` option to true.) * **HyBi drafts 07-12** (Use the option `protocolVersion: 8`) * **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`) diff --git a/doc/ws.md b/doc/ws.md index 401688806..b924e789e 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -14,7 +14,6 @@ This class is a WebSocket server. It is an `EventEmitter`. * `handleProtocols` Function * `path` String * `noServer` Boolean - * `disableHixie` Boolean * `clientTracking` Boolean * `perMessageDeflate` Boolean|Object * `callback` Function @@ -107,7 +106,7 @@ This class represents a WebSocket connection. It is an `EventEmitter`. * `protocol` String * `agent` Agent * `headers` Object - * `protocolVersion` Number|String + * `protocolVersion` Number|String -- the following only apply if `address` is a String * `host` String * `origin` String @@ -137,7 +136,7 @@ Possible states are `WebSocket.CONNECTING`, `WebSocket.OPEN`, `WebSocket.CLOSING ### websocket.protocolVersion -The WebSocket protocol version used for this connection, `8`, `13` or `hixie-76` (the latter only for server clients). +The WebSocket protocol version used for this connection, `8`, `13`. ### websocket.url diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js deleted file mode 100644 index cda7ecdb6..000000000 --- a/lib/Receiver.hixie.js +++ /dev/null @@ -1,170 +0,0 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - -'use strict'; - -/** - * State constants - */ - -const EMPTY = 0; -const BODY = 1; -const BINARYLENGTH = 2; -const BINARYBODY = 3; - -/** - * Hixie Receiver implementation - */ - -class Receiver { - constructor () { - this.state = EMPTY; - this.buffers = []; - this.messageEnd = -1; - this.spanLength = 0; - this.dead = false; - - this.onerror = function () {}; - this.ontext = function () {}; - this.onbinary = function () {}; - this.onclose = function () {}; - this.onping = function () {}; - this.onpong = function () {}; - } - - /** - * Add new data to the parser. - * - * @api public - */ - - add (data) { - var self = this; - function doAdd () { - if (self.state === EMPTY) { - if (data.length === 2 && data[0] === 0xFF && data[1] === 0x00) { - self.reset(); - self.onclose(); - return; - } - if (data[0] === 0x80) { - self.messageEnd = 0; - self.state = BINARYLENGTH; - data = data.slice(1); - } else { - if (data[0] !== 0x00) { - self.error(new Error('payload must start with 0x00 byte'), true); - return; - } - data = data.slice(1); - self.state = BODY; - } - } - if (self.state === BINARYLENGTH) { - var i = 0; - while ((i < data.length) && (data[i] & 0x80)) { - self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); - ++i; - } - if (i < data.length) { - self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); - self.state = BINARYBODY; - ++i; - } - if (i > 0) { - data = data.slice(i); - } - } - if (self.state === BINARYBODY) { - var dataleft = self.messageEnd - self.spanLength; - if (data.length >= dataleft) { - // consume the whole buffer to finish the frame - self.buffers.push(data); - self.spanLength += dataleft; - self.messageEnd = dataleft; - return self.parse(); - } - // frame's not done even if we consume it all - self.buffers.push(data); - self.spanLength += data.length; - return; - } - self.buffers.push(data); - if ((self.messageEnd = data.indexOf(0xFF)) !== -1) { - self.spanLength += self.messageEnd; - return self.parse(); - } else { - self.spanLength += data.length; - } - } - while (data) data = doAdd(); - } - - /** - * Releases all resources used by the receiver. - * - * @api public - */ - - cleanup () { - this.dead = true; - this.state = EMPTY; - this.buffers = []; - } - - /** - * Process buffered data. - * - * @api public - */ - - parse () { - var output = new Buffer(this.spanLength); - var outputIndex = 0; - for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) { - var buffer = this.buffers[bi]; - buffer.copy(output, outputIndex); - outputIndex += buffer.length; - } - var lastBuffer = this.buffers[this.buffers.length - 1]; - if (this.messageEnd > 0) lastBuffer.copy(output, outputIndex, 0, this.messageEnd); - if (this.state !== BODY) --this.messageEnd; - var tail = null; - if (this.messageEnd < lastBuffer.length - 1) { - tail = lastBuffer.slice(this.messageEnd + 1); - } - this.reset(); - this.ontext(output.toString('utf8')); - return tail; - } - - /** - * Handles an error - * - * @api private - */ - - error (err, terminate) { - this.reset(); - this.onerror(err, terminate); - return this; - } - - /** - * Reset parser state - * - * @api private - */ - reset (reason) { - if (this.dead) return; - this.state = EMPTY; - this.buffers = []; - this.messageEnd = -1; - this.spanLength = 0; - } -} - -module.exports = Receiver; diff --git a/lib/Sender.hixie.js b/lib/Sender.hixie.js deleted file mode 100644 index 439eb302f..000000000 --- a/lib/Sender.hixie.js +++ /dev/null @@ -1,112 +0,0 @@ -/*! - * ws: a node.js websocket client - * Copyright(c) 2011 Einar Otto Stangvik - * MIT Licensed - */ - -'use strict'; - -const EventEmitter = require('events'); - -/** - * Hixie Sender implementation, Inherits from EventEmitter. - */ - -class Sender extends EventEmitter { - constructor (socket) { - super(); - - this.socket = socket; - this.continuationFrame = false; - this.isClosed = false; - } - - /** - * Frames and writes data. - * - * @api public - */ - send (data, options, cb) { - if (this.isClosed) return; - - var isString = typeof data === 'string'; - var length = isString ? Buffer.byteLength(data) : data.length; - var lengthbytes = (length > 127) ? 2 : 1; // assume less than 2**14 bytes - var writeStartMarker = this.continuationFrame === false; - var writeEndMarker = !options || !(typeof options.fin !== 'undefined' && !options.fin); - - var bufferLength = writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0; - bufferLength += length; - bufferLength += (writeEndMarker && !(options && options.binary)) ? 1 : 0; - - var buffer = new Buffer(bufferLength); - var offset = writeStartMarker ? 1 : 0; - - if (writeStartMarker) { - if (options && options.binary) { - buffer.write('\x80', 'binary'); - // assume length less than 2**14 bytes - if (lengthbytes > 1) { - buffer.write(String.fromCharCode(128 + length / 128), offset++, 'binary'); - } - buffer.write(String.fromCharCode(length & 0x7f), offset++, 'binary'); - } else { - buffer.write('\x00', 'binary'); - } - } - - if (isString) buffer.write(data, offset, 'utf8'); - else data.copy(buffer, offset, 0); - - if (writeEndMarker) { - if (options && options.binary) { - // sending binary, not writing end marker - } else { - buffer.write('\xff', offset + length, 'binary'); - } - this.continuationFrame = false; - } else { - this.continuationFrame = true; - } - - try { - this.socket.write(buffer, 'binary', cb); - } catch (e) { - this.emit('error', e); - } - } - - /** - * Sends a close instruction to the remote party. - * - * @api public - */ - - close (code, data, mask, cb) { - if (this.isClosed) return; - this.isClosed = true; - try { - if (this.continuationFrame) this.socket.write(new Buffer([0xff], 'binary')); - this.socket.write(new Buffer([0xff, 0x00]), 'binary', cb); - } catch (e) { - this.emit('error', e); - } - } - - /** - * Sends a ping message to the remote party. Not available for hixie. - * - * @api public - */ - - ping (data, options) {} - - /** - * Sends a pong message to the remote party. Not available for hixie. - * - * @api public - */ - pong (data, options) {} -} - -module.exports = Sender; diff --git a/lib/WebSocket.js b/lib/WebSocket.js index f4a63d910..0931a1c8e 100644 --- a/lib/WebSocket.js +++ b/lib/WebSocket.js @@ -15,8 +15,6 @@ const stream = require('stream'); const Ultron = require('ultron'); const Sender = require('./Sender'); const Receiver = require('./Receiver'); -const SenderHixie = require('./Sender.hixie'); -const ReceiverHixie = require('./Receiver.hixie'); const Extensions = require('./Extensions'); const PerMessageDeflate = require('./PerMessageDeflate'); const EventEmitter = require('events'); @@ -72,7 +70,7 @@ function WebSocket (address, protocols, options) { this._closeReceived = false; this.bytesReceived = 0; this.readyState = null; - this.supports = {}; + this.supports = { binary: true }; this.extensions = {}; this._binaryType = 'nodebuffer'; @@ -539,16 +537,11 @@ function buildHostHeader (isSecure, hostname, port) { function initAsServerClient (req, socket, upgradeHead, options) { // expose state properties Object.assign(this, options); - this.supports.binary = this.protocolVersion !== 'hixie-76'; this.readyState = WebSocket.CONNECTING; this.upgradeReq = req; this._isServer = true; // establish connection - if (options.protocolVersion === 'hixie-76') { - establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead); - } else { - establishConnection.call(this, Receiver, Sender, socket, upgradeHead); - } + establishConnection.call(this, socket, upgradeHead); } function initAsClient (address, protocols, options) { @@ -599,7 +592,6 @@ function initAsClient (address, protocols, options) { this._isServer = false; this.url = address; this.protocolVersion = options.protocolVersion; - this.supports.binary = true; // begin handshake var key = new Buffer(options.protocolVersion + '-' + Date.now()).toString('base64'); @@ -772,7 +764,7 @@ function initAsClient (address, protocols, options) { this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; } - establishConnection.call(this, Receiver, Sender, socket, upgradeHead); + establishConnection.call(this, socket, upgradeHead); // perform cleanup on http resources req.removeAllListeners(); @@ -784,13 +776,13 @@ function initAsClient (address, protocols, options) { this.readyState = WebSocket.CONNECTING; } -function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { +function establishConnection (socket, upgradeHead) { var ultron = this._ultron = new Ultron(socket); socket.setTimeout(0); socket.setNoDelay(true); - this._receiver = new ReceiverClass(this.extensions, this.maxPayload); + this._receiver = new Receiver(this.extensions, this.maxPayload); this._socket = socket; // socket cleanup handlers @@ -847,7 +839,7 @@ function establishConnection (ReceiverClass, SenderClass, socket, upgradeHead) { }; // finalize the client - this._sender = new SenderClass(socket, this.extensions); + this._sender = new Sender(socket, this.extensions); this._sender.onerror = (error) => { this.close(1002, ''); this.emit('error', error); diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js index 34848b3ff..e51c2e1c5 100644 --- a/lib/WebSocketServer.js +++ b/lib/WebSocketServer.js @@ -38,7 +38,6 @@ function WebSocketServer (options, callback) { handleProtocols: null, path: null, noServer: false, - disableHixie: false, clientTracking: true, perMessageDeflate: true, maxPayload: 100 * 1024 * 1024, @@ -173,12 +172,11 @@ WebSocketServer.prototype.handleUpgrade = function (req, socket, upgradeHead, cb } } - if (!req.headers.upgrade || req.headers.upgrade.toLowerCase() !== 'websocket') { + if (!req.headers.upgrade || req.headers.upgrade !== 'websocket') { return abortConnection(socket, 400); } - if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments); - else handleHybiUpgrade.apply(this, arguments); + handleHybiUpgrade.apply(this, arguments); }; module.exports = WebSocketServer; @@ -325,180 +323,6 @@ function handleHybiUpgrade (req, socket, upgradeHead, cb) { completeHybiUpgrade1(); } -function handleHixieUpgrade (req, socket, upgradeHead, cb) { - // handle premature socket errors - var errorHandler = () => { - try { socket.destroy(); } catch (e) {} - }; - socket.on('error', errorHandler); - - // bail if options prevent hixie - if (this.options.disableHixie) { - return abortConnection(socket, 401, 'Hixie support disabled'); - } - - // verify key presence - if (!req.headers['sec-websocket-key2']) { - return abortConnection(socket, 400); - } - - var origin = req.headers['origin']; - - // setup handshake completion to run after client has been verified - var onClientVerified = () => { - var wshost; - if (!req.headers['x-forwarded-host']) { - wshost = req.headers.host; - } else { - wshost = req.headers['x-forwarded-host']; - } - - var proto = (req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws'; - var location = `${proto}://${wshost}${req.url}`; - var protocol = req.headers['sec-websocket-protocol']; - - // build the response header and return a Buffer - var buildResponseHeader = () => { - var headers = [ - 'HTTP/1.1 101 Switching Protocols', - 'Upgrade: WebSocket', - 'Connection: Upgrade', - 'Sec-WebSocket-Location: ' + location - ]; - if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`); - if (origin) headers.push(`Sec-WebSocket-Origin: ${origin}`); - - return new Buffer(headers.concat('', '').join('\r\n')); - }; - - // send handshake response before receiving the nonce - var handshakeResponse = () => { - socket.setTimeout(0); - socket.setNoDelay(true); - - var headerBuffer = buildResponseHeader(); - - try { - socket.write(headerBuffer, 'binary', (err) => { - // remove listener if there was an error - if (err) socket.removeListener('data', handler); - }); - } catch (e) { - try { socket.destroy(); } catch (e) {} - } - }; - - // handshake completion code to run once nonce has been successfully retrieved - var completeHandshake = (nonce, rest, headerBuffer) => { - // calculate key - var k1 = req.headers['sec-websocket-key1']; - var k2 = req.headers['sec-websocket-key2']; - var md5 = crypto.createHash('md5'); - - [k1, k2].forEach((k) => { - var n = parseInt(k.replace(/[^\d]/g, '')); - var spaces = k.replace(/[^ ]/g, '').length; - if (spaces === 0 || n % spaces !== 0) { - return abortConnection(socket, 400); - } - n /= spaces; - md5.update(String.fromCharCode( - n >> 24 & 0xFF, - n >> 16 & 0xFF, - n >> 8 & 0xFF, - n & 0xFF), 'binary'); - }); - md5.update(nonce.toString('binary'), 'binary'); - - socket.setTimeout(0); - socket.setNoDelay(true); - - try { - 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', (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 (this.clients) { - this.clients.add(client); - client.on('close', () => this.clients.delete(client)); - } - - // signal upgrade complete - socket.removeListener('error', errorHandler); - cb(client); - }); - } catch (e) { - try { socket.destroy(); } catch (e) {} - return; - } - }; - - // retrieve nonce - var nonceLength = 8; - var nonce, rest; - if (upgradeHead && upgradeHead.length >= nonceLength) { - nonce = upgradeHead.slice(0, nonceLength); - rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; - completeHandshake(nonce, rest, buildResponseHeader()); - } else { - // nonce not present in upgradeHead - nonce = new Buffer(nonceLength); - upgradeHead.copy(nonce, 0); - var received = upgradeHead.length; - rest = null; - var handler = (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); - - // complete the handshake but send empty buffer for headers since they have already been sent - completeHandshake(nonce, rest, new Buffer(0)); - } - }; - - // handle additional data as we receive it - socket.on('data', handler); - - // send header response before we have the nonce to fix haproxy buffering - handshakeResponse(); - } - }; - - // verify client - if (typeof this.options.verifyClient === 'function') { - var info = { - secure: req.connection.authorized !== undefined || req.connection.encrypted !== undefined, - origin: origin, - req: req - }; - if (this.options.verifyClient.length === 2) { - this.options.verifyClient(info, (result, code, message) => { - if (!result) return abortConnection(socket, code || 401, message); - - onClientVerified(); - }); - return; - } else if (!this.options.verifyClient(info)) { - return abortConnection(socket, 401); - } - } - - // no client verification required - onClientVerified(); -} - function acceptExtensions (offer) { var extensions = {}; var options = this.options.perMessageDeflate; @@ -522,11 +346,14 @@ function acceptExtensions (offer) { function abortConnection (socket, code, message) { if (socket.writable) { message = message || http.STATUS_CODES[code]; - socket.write(`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n`); - socket.write('Connection: close\r\n'); - socket.write('Content-type: text/html\r\n'); - socket.write(`Content-Length: ${Buffer.byteLength(message)}\r\n\r\n`); - socket.write(message); + socket.write( + `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` + + 'Connection: close\r\n' + + 'Content-type: text/html\r\n' + + `Content-Length: ${Buffer.byteLength(message)}\r\n` + + '\r\n' + + message + ); } socket.destroy(); } diff --git a/package.json b/package.json index 29cf55b19..888898f60 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "license": "MIT", "main": "index.js", "keywords": [ - "Hixie", "HyBi", "Push", "RFC-6455", @@ -32,7 +31,6 @@ "eslint-config-standard": "6.2.x", "eslint-plugin-promise": "3.0.x", "eslint-plugin-standard": "2.0.x", - "expect.js": "0.3.x", "istanbul": "0.4.x", "mocha": "3.1.x", "should": "8.0.x", diff --git a/test/Receiver.hixie.test.js b/test/Receiver.hixie.test.js deleted file mode 100644 index 8646d7683..000000000 --- a/test/Receiver.hixie.test.js +++ /dev/null @@ -1,170 +0,0 @@ -var assert = require('assert') - , expect = require('expect.js') - , Receiver = require('../lib/Receiver.hixie'); -require('./hybi-common'); - -describe('Receiver', function() { - describe('#ctor', function() { - it('throws TypeError when called without new', function(done) { - try { - var p = Receiver(); - } - catch (e) { - e.should.be.instanceof(TypeError); - done(); - } - }); - }); - - it('can parse text message', function() { - var p = new Receiver(); - var packet = '00 48 65 6c 6c 6f ff'; - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal('Hello', data); - }; - - p.add(getBufferFromHexString(packet)); - expect(gotData).to.equal(true); - }); - - it('can parse multiple text messages', function() { - var p = new Receiver(); - var packet = '00 48 65 6c 6c 6f ff 00 48 65 6c 6c 6f ff'; - - var gotData = false; - var messages = []; - p.ontext = function(data) { - gotData = true; - messages.push(data); - }; - - p.add(getBufferFromHexString(packet)); - expect(gotData).to.equal(true); - for (var i = 0; i < 2; ++i) { - expect(messages[i]).to.equal('Hello'); - } - }); - - it('can parse empty message', function() { - var p = new Receiver(); - var packet = '00 ff'; - - var gotData = false; - p.ontext = function(data) { - gotData = true; - assert.equal('', data); - }; - - p.add(getBufferFromHexString(packet)); - expect(gotData).to.equal(true); - }); - - it('can parse text messages delivered over multiple frames', function() { - var p = new Receiver(); - var packets = [ - '00 48', - '65 6c 6c', - '6f ff 00 48', - '65', - '6c 6c 6f', - 'ff' - ]; - - var gotData = false; - var messages = []; - p.ontext = function(data) { - gotData = true; - messages.push(data); - }; - - for (var i = 0; i < packets.length; ++i) { - p.add(getBufferFromHexString(packets[i])); - } - expect(gotData).to.equal(true); - for (var i = 0; i < 2; ++i) { - expect(messages[i]).to.equal('Hello'); - } - }); - - it('emits an error if a payload doesnt start with 0x00', function() { - var p = new Receiver(); - var packets = [ - '00 6c ff', - '00 6c ff ff', - 'ff 00 6c ff 00 6c ff', - '00', - '6c 6c 6f', - 'ff' - ]; - - var gotData = false; - var gotError = false; - var messages = []; - p.ontext = function(data) { - gotData = true; - messages.push(data); - }; - p.onerror = function(reason, code) { - gotError = code == true; - }; - - for (var i = 0; i < packets.length && !gotError; ++i) { - p.add(getBufferFromHexString(packets[i])); - } - expect(gotError).to.equal(true); - expect(messages[0]).to.equal('l'); - expect(messages[1]).to.equal('l'); - expect(messages.length).to.equal(2); - }); - - it('can parse close messages', function() { - var p = new Receiver(); - var packets = [ - 'ff 00' - ]; - - var gotClose = false; - var gotError = false; - p.onclose = function() { - gotClose = true; - }; - p.onerror = function(reason, code) { - gotError = code == true; - }; - - for (var i = 0; i < packets.length && !gotError; ++i) { - p.add(getBufferFromHexString(packets[i])); - } - expect(gotClose).to.equal(true); - expect(gotError).to.equal(false); - }); - - it('can parse binary messages delivered over multiple frames', function() { - var p = new Receiver(); - var packets = [ - '80 05 48', - '65 6c 6c', - '6f 80 80 05 48', - '65', - '6c 6c 6f' - ]; - - var gotData = false; - var messages = []; - p.ontext = function(data) { - gotData = true; - messages.push(data); - }; - - for (var i = 0; i < packets.length; ++i) { - p.add(getBufferFromHexString(packets[i])); - } - expect(gotData).to.equal(true); - for (var i = 0; i < 2; ++i) { - expect(messages[i]).to.equal('Hello'); - } - }); -}); diff --git a/test/Sender.hixie.test.js b/test/Sender.hixie.test.js deleted file mode 100644 index 3bf3e6474..000000000 --- a/test/Sender.hixie.test.js +++ /dev/null @@ -1,146 +0,0 @@ -var assert = require('assert') - , Sender = require('../lib/Sender.hixie'); -require('should'); -require('./hybi-common'); - -describe('Sender', function() { - describe('#ctor', function() { - it('throws TypeError when called without new', function(done) { - try { - var sender = Sender({ write: function() {} }); - } - catch (e) { - e.should.be.instanceof(TypeError); - done(); - } - }); - }); - - describe('#send', function() { - it('frames and sends a text message', function(done) { - var message = 'Hello world'; - var received; - var socket = { - write: function(data, encoding, cb) { - received = data; - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(message, {}, function() { - received.toString('utf8').should.eql('\u0000' + message + '\ufffd'); - done(); - }); - }); - - it('frames and sends an empty message', function(done) { - var socket = { - write: function(data, encoding, cb) { - done(); - } - }; - var sender = new Sender(socket, {}); - sender.send('', {}, function() {}); - }); - - it('frames and sends a buffer', function(done) { - var received; - var socket = { - write: function(data, encoding, cb) { - received = data; - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(new Buffer('foobar'), {}, function() { - received.toString('utf8').should.eql('\u0000foobar\ufffd'); - done(); - }); - }); - - it('frames and sends a binary message', function(done) { - var message = 'Hello world'; - var received; - var socket = { - write: function(data, encoding, cb) { - received = data; - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(message, {binary: true}, function() { - received.toString('hex').should.eql( - // 0x80 0x0b H e l l o w o r l d - '800b48656c6c6f20776f726c64'); - done(); - }); - }); -/* - it('throws an exception for binary data', function(done) { - var socket = { - write: function(data, encoding, cb) { - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.on('error', function() { - done(); - }); - sender.send(new Buffer(100), {binary: true}, function() {}); - }); -*/ - it('can fauxe stream data', function(done) { - var received = []; - var socket = { - write: function(data, encoding, cb) { - received.push(data); - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(new Buffer('foobar'), { fin: false }, function() {}); - sender.send('bazbar', { fin: false }, function() {}); - sender.send(new Buffer('end'), { fin: true }, function() { - received[0].toString('utf8').should.eql('\u0000foobar'); - received[1].toString('utf8').should.eql('bazbar'); - received[2].toString('utf8').should.eql('end\ufffd'); - done(); - }); - }); - }); - - describe('#close', function() { - it('sends a hixie close frame', function(done) { - var received; - var socket = { - write: function(data, encoding, cb) { - received = data; - process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.close(null, null, null, function() { - received.toString('utf8').should.eql('\ufffd\u0000'); - done(); - }); - }); - - it('sends a message end marker if fauxe streaming has started, before hixie close frame', function(done) { - var received = []; - var socket = { - write: function(data, encoding, cb) { - received.push(data); - if (cb) process.nextTick(cb); - } - }; - var sender = new Sender(socket, {}); - sender.send(new Buffer('foobar'), { fin: false }, function() {}); - sender.close(null, null, null, function() { - received[0].toString('utf8').should.eql('\u0000foobar'); - received[1].toString('utf8').should.eql('\ufffd'); - received[2].toString('utf8').should.eql('\ufffd\u0000'); - done(); - }); - }); - }); -}); diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js index f6937833d..d50080c63 100644 --- a/test/WebSocket.test.js +++ b/test/WebSocket.test.js @@ -1921,7 +1921,7 @@ describe('WebSocket', function() { describe('protocol support discovery', function() { describe('#supports', function() { describe('#binary', function() { - it('returns true for hybi transport', function(done) { + it('returns true', function(done) { var wss = new WebSocketServer({port: ++port}, function() { var ws = new WebSocket('ws://localhost:' + port); }); @@ -1931,29 +1931,6 @@ describe('WebSocket', function() { done(); }); }); - - it('returns false for hixie transport', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - }); - wss.on('connection', function(client) { - assert.equal(false, client.supports.binary); - wss.close(); - done(); - }); - }); }); }); }); diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js index d6d333015..e8a08d80c 100644 --- a/test/WebSocketServer.test.js +++ b/test/WebSocketServer.test.js @@ -342,10 +342,10 @@ describe('WebSocketServer', function() { }); }); - describe('#maxpayload #hybiOnly', function() { + describe('#maxpayload', function() { it('maxpayload is passed on to clients,', function(done) { var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); @@ -358,7 +358,7 @@ describe('WebSocketServer', function() { }); it('maxpayload is passed on to hybi receivers', function(done) { var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); @@ -372,7 +372,7 @@ describe('WebSocketServer', function() { it('maxpayload is passed on to permessage-deflate', function(done) { var PerMessageDeflate = require('../lib/PerMessageDeflate'); var _maxPayload = 20480; - var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() { + var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload}, function() { wss.clients.size.should.eql(0); var ws = new WebSocket('ws://localhost:' + port); }); @@ -405,7 +405,7 @@ describe('WebSocketServer', function() { }); }); - it('can not finish upgrade when path is not right', function(done) { + it('closes the connection when path does not match', function (done) { var wss = new WebSocketServer({port: ++port, path: '/ws'}, function() { var options = { port: port, @@ -413,7 +413,7 @@ describe('WebSocketServer', function() { headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' - }, + } }; var req = http.request(options); req.end(); @@ -424,6 +424,28 @@ describe('WebSocketServer', function() { }); }); }); + + it('closes the connection when protocol version is Hixie-76', function (done) { + var wss = new WebSocketServer({port: ++port}, function () { + var options = { + port: port, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', + 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', + 'Sec-WebSocket-Protocol': 'sample' + } + }; + var req = http.request(options); + req.on('response', function (res) { + res.statusCode.should.eql(400); + wss.close(); + done(); + }); + req.end(); + }); + }); }); describe('hybi mode', function() { @@ -968,351 +990,6 @@ describe('WebSocketServer', function() { }); }); - describe('hixie mode', function() { - it('can be disabled', function(done) { - var wss = new WebSocketServer({port: ++port, disableHixie: true}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(401); - process.nextTick(function() { - wss.close(); - done(); - }); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - describe('connection establishing', function() { - it('does not accept connections with no sec-websocket-key1', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(400); - wss.close(); - done(); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('does not accept connections with no sec-websocket-key2', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(400); - wss.close(); - done(); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('accepts connections with valid handshake', function(done) { - var wss = new WebSocketServer({port: ++port}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - }); - wss.on('connection', function(ws) { - ws.terminate(); - wss.close(); - done(); - }); - wss.on('error', function() {}); - }); - - it('client can be denied', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { - return false; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(401); - process.nextTick(function() { - wss.close(); - done(); - }); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('client can be accepted', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { - return true; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - }); - wss.on('connection', function(ws) { - ws.terminate(); - wss.close(); - done(); - }); - wss.on('error', function() {}); - }); - - it('verifyClient gets client origin', function(done) { - var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { - info.origin.should.eql('http://foobarbaz.com'); - verifyClientCalled = true; - return false; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - verifyClientCalled.should.be.ok; - wss.close(); - done(); - }); - }); - wss.on('error', function() {}); - }); - - it('verifyClient gets original request', function(done) { - var verifyClientCalled = false; - var wss = new WebSocketServer({port: ++port, verifyClient: function(info) { - info.req.headers['sec-websocket-key1'].should.eql('3e6b263 4 17 80'); - verifyClientCalled = true; - return false; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - verifyClientCalled.should.be.ok; - wss.close(); - done(); - }); - }); - wss.on('error', function() {}); - }); - - it('client can be denied asynchronously', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - cb(false); - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(401); - process.nextTick(function() { - wss.close(); - done(); - }); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('client can be denied asynchronously with custom response code', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - cb(false, 404, 'Not Found'); - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - req.on('response', function(res) { - res.statusCode.should.eql(404); - process.nextTick(function() { - wss.close(); - done(); - }); - }); - }); - wss.on('connection', function(ws) { - done(new Error('connection must not be established')); - }); - wss.on('error', function() {}); - }); - - it('client can be accepted asynchronously', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { - cb(true); - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Origin': 'http://foobarbaz.com', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.end(); - }); - wss.on('connection', function(ws) { - wss.close(); - done(); - }); - wss.on('error', function() {}); - }); - - it('handles messages passed along with the upgrade request (upgrade head)', function(done) { - var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { - return true; - }}, function() { - var options = { - port: port, - host: '127.0.0.1', - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '3e6b263 4 17 80', - 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90', - 'Origin': 'http://foobar.com' - } - }; - var req = http.request(options); - req.write('WjN}|M(6'); - req.write(new Buffer([0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff], 'binary')); - req.end(); - }); - wss.on('connection', function(ws) { - ws.on('message', function(data) { - data.should.eql('Hello'); - ws.terminate(); - wss.close(); - done(); - }); - }); - wss.on('error', function() {}); - }); - }); - }); - describe('client properties', function() { it('protocol is exposed', function(done) { var wss = new WebSocketServer({port: ++port}, function() { From cb601eae93375dbe8fd98d8f9260e999a2ebe94a Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 24 Oct 2016 14:27:00 +0200 Subject: [PATCH 2/2] !fixup: remove leftover --- doc/ws.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ws.md b/doc/ws.md index b924e789e..f68c99cf3 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -106,7 +106,7 @@ This class represents a WebSocket connection. It is an `EventEmitter`. * `protocol` String * `agent` Agent * `headers` Object - * `protocolVersion` Number|String + * `protocolVersion` Number -- the following only apply if `address` is a String * `host` String * `origin` String