added unit testing, and started implementing unit tests...phew

This commit is contained in:
Josh Burman
2019-03-12 22:28:02 -04:00
parent 74aad4a957
commit e8c2539f1b
3489 changed files with 464813 additions and 88 deletions

52
node_modules/websocket/lib/BufferUtil.fallback.js generated vendored Normal file
View File

@ -0,0 +1,52 @@
/*!
* Copied from:
* ws: a node.js websocket client
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
* MIT Licensed
*/
/* jshint -W086 */
module.exports.BufferUtil = {
merge: function(mergedBuffer, buffers) {
var offset = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
var buf = buffers[i];
buf.copy(mergedBuffer, offset);
offset += buf.length;
}
},
mask: function(source, mask, output, offset, length) {
var maskNum = mask.readUInt32LE(0);
var i = 0;
for (; i < length - 3; i += 4) {
var num = maskNum ^ source.readUInt32LE(i);
if (num < 0) { num = 4294967296 + num; }
output.writeUInt32LE(num, offset + i);
}
switch (length % 4) {
case 3: output[offset + i + 2] = source[i + 2] ^ mask[2];
case 2: output[offset + i + 1] = source[i + 1] ^ mask[1];
case 1: output[offset + i] = source[i] ^ mask[0];
case 0:
}
},
unmask: function(data, mask) {
var maskNum = mask.readUInt32LE(0);
var length = data.length;
var i = 0;
for (; i < length - 3; i += 4) {
var num = maskNum ^ data.readUInt32LE(i);
if (num < 0) { num = 4294967296 + num; }
data.writeUInt32LE(num, i);
}
switch (length % 4) {
case 3: data[i + 2] = data[i + 2] ^ mask[2];
case 2: data[i + 1] = data[i + 1] ^ mask[1];
case 1: data[i] = data[i] ^ mask[0];
case 0:
}
}
};
/* jshint +W086 */

17
node_modules/websocket/lib/BufferUtil.js generated vendored Normal file
View File

@ -0,0 +1,17 @@
/*!
* Copied from:
* ws: a node.js websocket client
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
* MIT Licensed
*/
try {
module.exports = require('../build/Release/bufferutil');
} catch (e) { try {
module.exports = require('../build/default/bufferutil');
} catch (e) { try {
module.exports = require('./BufferUtil.fallback');
} catch (e) {
console.error('bufferutil.node seems to not have been built. Run npm install.');
throw e;
}}}

32
node_modules/websocket/lib/Deprecation.js generated vendored Normal file
View File

@ -0,0 +1,32 @@
/************************************************************************
* Copyright 2010-2015 Brian McKelvey.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
var Deprecation = {
disableWarnings: false,
deprecationWarningMap: {
},
warn: function(deprecationName) {
if (!this.disableWarnings && this.deprecationWarningMap[deprecationName]) {
console.warn('DEPRECATION WARNING: ' + this.deprecationWarningMap[deprecationName]);
this.deprecationWarningMap[deprecationName] = false;
}
}
};
module.exports = Deprecation;

12
node_modules/websocket/lib/Validation.fallback.js generated vendored Normal file
View File

@ -0,0 +1,12 @@
/*!
* UTF-8 Validation Fallback Code originally from:
* ws: a node.js websocket client
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
* MIT Licensed
*/
module.exports.Validation = {
isValidUTF8: function() {
return true;
}
};

17
node_modules/websocket/lib/Validation.js generated vendored Normal file
View File

@ -0,0 +1,17 @@
/*!
* UTF-8 Validation Code originally from:
* ws: a node.js websocket client
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
* MIT Licensed
*/
try {
module.exports = require('../build/Release/validation');
} catch (e) { try {
module.exports = require('../build/default/validation');
} catch (e) { try {
module.exports = require('./Validation.fallback');
} catch (e) {
console.error('validation.node seems not to have been built. Run npm install.');
throw e;
}}}

257
node_modules/websocket/lib/W3CWebSocket.js generated vendored Normal file
View File

@ -0,0 +1,257 @@
/************************************************************************
* Copyright 2010-2015 Brian McKelvey.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
var WebSocketClient = require('./WebSocketClient');
var toBuffer = require('typedarray-to-buffer');
var yaeti = require('yaeti');
const CONNECTING = 0;
const OPEN = 1;
const CLOSING = 2;
const CLOSED = 3;
module.exports = W3CWebSocket;
function W3CWebSocket(url, protocols, origin, headers, requestOptions, clientConfig) {
// Make this an EventTarget.
yaeti.EventTarget.call(this);
// Sanitize clientConfig.
clientConfig = clientConfig || {};
clientConfig.assembleFragments = true; // Required in the W3C API.
var self = this;
this._url = url;
this._readyState = CONNECTING;
this._protocol = undefined;
this._extensions = '';
this._bufferedAmount = 0; // Hack, always 0.
this._binaryType = 'arraybuffer'; // TODO: Should be 'blob' by default, but Node has no Blob.
// The WebSocketConnection instance.
this._connection = undefined;
// WebSocketClient instance.
this._client = new WebSocketClient(clientConfig);
this._client.on('connect', function(connection) {
onConnect.call(self, connection);
});
this._client.on('connectFailed', function() {
onConnectFailed.call(self);
});
this._client.connect(url, protocols, origin, headers, requestOptions);
}
// Expose W3C read only attributes.
Object.defineProperties(W3CWebSocket.prototype, {
url: { get: function() { return this._url; } },
readyState: { get: function() { return this._readyState; } },
protocol: { get: function() { return this._protocol; } },
extensions: { get: function() { return this._extensions; } },
bufferedAmount: { get: function() { return this._bufferedAmount; } }
});
// Expose W3C write/read attributes.
Object.defineProperties(W3CWebSocket.prototype, {
binaryType: {
get: function() {
return this._binaryType;
},
set: function(type) {
// TODO: Just 'arraybuffer' supported.
if (type !== 'arraybuffer') {
throw new SyntaxError('just "arraybuffer" type allowed for "binaryType" attribute');
}
this._binaryType = type;
}
}
});
// Expose W3C readyState constants into the WebSocket instance as W3C states.
[['CONNECTING',CONNECTING], ['OPEN',OPEN], ['CLOSING',CLOSING], ['CLOSED',CLOSED]].forEach(function(property) {
Object.defineProperty(W3CWebSocket.prototype, property[0], {
get: function() { return property[1]; }
});
});
// Also expose W3C readyState constants into the WebSocket class (not defined by the W3C,
// but there are so many libs relying on them).
[['CONNECTING',CONNECTING], ['OPEN',OPEN], ['CLOSING',CLOSING], ['CLOSED',CLOSED]].forEach(function(property) {
Object.defineProperty(W3CWebSocket, property[0], {
get: function() { return property[1]; }
});
});
W3CWebSocket.prototype.send = function(data) {
if (this._readyState !== OPEN) {
throw new Error('cannot call send() while not connected');
}
// Text.
if (typeof data === 'string' || data instanceof String) {
this._connection.sendUTF(data);
}
// Binary.
else {
// Node Buffer.
if (data instanceof Buffer) {
this._connection.sendBytes(data);
}
// If ArrayBuffer or ArrayBufferView convert it to Node Buffer.
else if (data.byteLength || data.byteLength === 0) {
data = toBuffer(data);
this._connection.sendBytes(data);
}
else {
throw new Error('unknown binary data:', data);
}
}
};
W3CWebSocket.prototype.close = function(code, reason) {
switch(this._readyState) {
case CONNECTING:
// NOTE: We don't have the WebSocketConnection instance yet so no
// way to close the TCP connection.
// Artificially invoke the onConnectFailed event.
onConnectFailed.call(this);
// And close if it connects after a while.
this._client.on('connect', function(connection) {
if (code) {
connection.close(code, reason);
} else {
connection.close();
}
});
break;
case OPEN:
this._readyState = CLOSING;
if (code) {
this._connection.close(code, reason);
} else {
this._connection.close();
}
break;
case CLOSING:
case CLOSED:
break;
}
};
/**
* Private API.
*/
function createCloseEvent(code, reason) {
var event = new yaeti.Event('close');
event.code = code;
event.reason = reason;
event.wasClean = (typeof code === 'undefined' || code === 1000);
return event;
}
function createMessageEvent(data) {
var event = new yaeti.Event('message');
event.data = data;
return event;
}
function onConnect(connection) {
var self = this;
this._readyState = OPEN;
this._connection = connection;
this._protocol = connection.protocol;
this._extensions = connection.extensions;
this._connection.on('close', function(code, reason) {
onClose.call(self, code, reason);
});
this._connection.on('message', function(msg) {
onMessage.call(self, msg);
});
this.dispatchEvent(new yaeti.Event('open'));
}
function onConnectFailed() {
destroy.call(this);
this._readyState = CLOSED;
try {
this.dispatchEvent(new yaeti.Event('error'));
} finally {
this.dispatchEvent(createCloseEvent(1006, 'connection failed'));
}
}
function onClose(code, reason) {
destroy.call(this);
this._readyState = CLOSED;
this.dispatchEvent(createCloseEvent(code, reason || ''));
}
function onMessage(message) {
if (message.utf8Data) {
this.dispatchEvent(createMessageEvent(message.utf8Data));
}
else if (message.binaryData) {
// Must convert from Node Buffer to ArrayBuffer.
// TODO: or to a Blob (which does not exist in Node!).
if (this.binaryType === 'arraybuffer') {
var buffer = message.binaryData;
var arraybuffer = new ArrayBuffer(buffer.length);
var view = new Uint8Array(arraybuffer);
for (var i=0, len=buffer.length; i<len; ++i) {
view[i] = buffer[i];
}
this.dispatchEvent(createMessageEvent(arraybuffer));
}
}
}
function destroy() {
this._client.removeAllListeners();
if (this._connection) {
this._connection.removeAllListeners();
}
}

361
node_modules/websocket/lib/WebSocketClient.js generated vendored Normal file
View File

@ -0,0 +1,361 @@
/************************************************************************
* Copyright 2010-2015 Brian McKelvey.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
var utils = require('./utils');
var extend = utils.extend;
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var http = require('http');
var https = require('https');
var url = require('url');
var crypto = require('crypto');
var WebSocketConnection = require('./WebSocketConnection');
var bufferAllocUnsafe = utils.bufferAllocUnsafe;
var protocolSeparators = [
'(', ')', '<', '>', '@',
',', ';', ':', '\\', '\"',
'/', '[', ']', '?', '=',
'{', '}', ' ', String.fromCharCode(9)
];
var excludedTlsOptions = ['hostname','port','method','path','headers'];
function WebSocketClient(config) {
// Superclass Constructor
EventEmitter.call(this);
// TODO: Implement extensions
this.config = {
// 1MiB max frame size.
maxReceivedFrameSize: 0x100000,
// 8MiB max message size, only applicable if
// assembleFragments is true
maxReceivedMessageSize: 0x800000,
// Outgoing messages larger than fragmentationThreshold will be
// split into multiple fragments.
fragmentOutgoingMessages: true,
// Outgoing frames are fragmented if they exceed this threshold.
// Default is 16KiB
fragmentationThreshold: 0x4000,
// Which version of the protocol to use for this session. This
// option will be removed once the protocol is finalized by the IETF
// It is only available to ease the transition through the
// intermediate draft protocol versions.
// At present, it only affects the name of the Origin header.
webSocketVersion: 13,
// If true, fragmented messages will be automatically assembled
// and the full message will be emitted via a 'message' event.
// If false, each frame will be emitted via a 'frame' event and
// the application will be responsible for aggregating multiple
// fragmented frames. Single-frame messages will emit a 'message'
// event in addition to the 'frame' event.
// Most users will want to leave this set to 'true'
assembleFragments: true,
// The Nagle Algorithm makes more efficient use of network resources
// by introducing a small delay before sending small packets so that
// multiple messages can be batched together before going onto the
// wire. This however comes at the cost of latency, so the default
// is to disable it. If you don't need low latency and are streaming
// lots of small messages, you can change this to 'false'
disableNagleAlgorithm: true,
// The number of milliseconds to wait after sending a close frame
// for an acknowledgement to come back before giving up and just
// closing the socket.
closeTimeout: 5000,
// Options to pass to https.connect if connecting via TLS
tlsOptions: {}
};
if (config) {
var tlsOptions;
if (config.tlsOptions) {
tlsOptions = config.tlsOptions;
delete config.tlsOptions;
}
else {
tlsOptions = {};
}
extend(this.config, config);
extend(this.config.tlsOptions, tlsOptions);
}
this._req = null;
switch (this.config.webSocketVersion) {
case 8:
case 13:
break;
default:
throw new Error('Requested webSocketVersion is not supported. Allowed values are 8 and 13.');
}
}
util.inherits(WebSocketClient, EventEmitter);
WebSocketClient.prototype.connect = function(requestUrl, protocols, origin, headers, extraRequestOptions) {
var self = this;
if (typeof(protocols) === 'string') {
if (protocols.length > 0) {
protocols = [protocols];
}
else {
protocols = [];
}
}
if (!(protocols instanceof Array)) {
protocols = [];
}
this.protocols = protocols;
this.origin = origin;
if (typeof(requestUrl) === 'string') {
this.url = url.parse(requestUrl);
}
else {
this.url = requestUrl; // in case an already parsed url is passed in.
}
if (!this.url.protocol) {
throw new Error('You must specify a full WebSocket URL, including protocol.');
}
if (!this.url.host) {
throw new Error('You must specify a full WebSocket URL, including hostname. Relative URLs are not supported.');
}
this.secure = (this.url.protocol === 'wss:');
// validate protocol characters:
this.protocols.forEach(function(protocol) {
for (var i=0; i < protocol.length; i ++) {
var charCode = protocol.charCodeAt(i);
var character = protocol.charAt(i);
if (charCode < 0x0021 || charCode > 0x007E || protocolSeparators.indexOf(character) !== -1) {
throw new Error('Protocol list contains invalid character "' + String.fromCharCode(charCode) + '"');
}
}
});
var defaultPorts = {
'ws:': '80',
'wss:': '443'
};
if (!this.url.port) {
this.url.port = defaultPorts[this.url.protocol];
}
var nonce = bufferAllocUnsafe(16);
for (var i=0; i < 16; i++) {
nonce[i] = Math.round(Math.random()*0xFF);
}
this.base64nonce = nonce.toString('base64');
var hostHeaderValue = this.url.hostname;
if ((this.url.protocol === 'ws:' && this.url.port !== '80') ||
(this.url.protocol === 'wss:' && this.url.port !== '443')) {
hostHeaderValue += (':' + this.url.port);
}
var reqHeaders = {};
if (this.secure && this.config.tlsOptions.hasOwnProperty('headers')) {
// Allow for additional headers to be provided when connecting via HTTPS
extend(reqHeaders, this.config.tlsOptions.headers);
}
if (headers) {
// Explicitly provided headers take priority over any from tlsOptions
extend(reqHeaders, headers);
}
extend(reqHeaders, {
'Upgrade': 'websocket',
'Connection': 'Upgrade',
'Sec-WebSocket-Version': this.config.webSocketVersion.toString(10),
'Sec-WebSocket-Key': this.base64nonce,
'Host': reqHeaders.Host || hostHeaderValue
});
if (this.protocols.length > 0) {
reqHeaders['Sec-WebSocket-Protocol'] = this.protocols.join(', ');
}
if (this.origin) {
if (this.config.webSocketVersion === 13) {
reqHeaders['Origin'] = this.origin;
}
else if (this.config.webSocketVersion === 8) {
reqHeaders['Sec-WebSocket-Origin'] = this.origin;
}
}
// TODO: Implement extensions
var pathAndQuery;
// Ensure it begins with '/'.
if (this.url.pathname) {
pathAndQuery = this.url.path;
}
else if (this.url.path) {
pathAndQuery = '/' + this.url.path;
}
else {
pathAndQuery = '/';
}
function handleRequestError(error) {
self._req = null;
self.emit('connectFailed', error);
}
var requestOptions = {
agent: false
};
if (extraRequestOptions) {
extend(requestOptions, extraRequestOptions);
}
// These options are always overridden by the library. The user is not
// allowed to specify these directly.
extend(requestOptions, {
hostname: this.url.hostname,
port: this.url.port,
method: 'GET',
path: pathAndQuery,
headers: reqHeaders
});
if (this.secure) {
var tlsOptions = this.config.tlsOptions;
for (var key in tlsOptions) {
if (tlsOptions.hasOwnProperty(key) && excludedTlsOptions.indexOf(key) === -1) {
requestOptions[key] = tlsOptions[key];
}
}
}
var req = this._req = (this.secure ? https : http).request(requestOptions);
req.on('upgrade', function handleRequestUpgrade(response, socket, head) {
self._req = null;
req.removeListener('error', handleRequestError);
self.socket = socket;
self.response = response;
self.firstDataChunk = head;
self.validateHandshake();
});
req.on('error', handleRequestError);
req.on('response', function(response) {
self._req = null;
if (utils.eventEmitterListenerCount(self, 'httpResponse') > 0) {
self.emit('httpResponse', response, self);
if (response.socket) {
response.socket.end();
}
}
else {
var headerDumpParts = [];
for (var headerName in response.headers) {
headerDumpParts.push(headerName + ': ' + response.headers[headerName]);
}
self.failHandshake(
'Server responded with a non-101 status: ' +
response.statusCode + ' ' + response.statusMessage +
'\nResponse Headers Follow:\n' +
headerDumpParts.join('\n') + '\n'
);
}
});
req.end();
};
WebSocketClient.prototype.validateHandshake = function() {
var headers = this.response.headers;
if (this.protocols.length > 0) {
this.protocol = headers['sec-websocket-protocol'];
if (this.protocol) {
if (this.protocols.indexOf(this.protocol) === -1) {
this.failHandshake('Server did not respond with a requested protocol.');
return;
}
}
else {
this.failHandshake('Expected a Sec-WebSocket-Protocol header.');
return;
}
}
if (!(headers['connection'] && headers['connection'].toLocaleLowerCase() === 'upgrade')) {
this.failHandshake('Expected a Connection: Upgrade header from the server');
return;
}
if (!(headers['upgrade'] && headers['upgrade'].toLocaleLowerCase() === 'websocket')) {
this.failHandshake('Expected an Upgrade: websocket header from the server');
return;
}
var sha1 = crypto.createHash('sha1');
sha1.update(this.base64nonce + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11');
var expectedKey = sha1.digest('base64');
if (!headers['sec-websocket-accept']) {
this.failHandshake('Expected Sec-WebSocket-Accept header from server');
return;
}
if (headers['sec-websocket-accept'] !== expectedKey) {
this.failHandshake('Sec-WebSocket-Accept header from server didn\'t match expected value of ' + expectedKey);
return;
}
// TODO: Support extensions
this.succeedHandshake();
};
WebSocketClient.prototype.failHandshake = function(errorDescription) {
if (this.socket && this.socket.writable) {
this.socket.end();
}
this.emit('connectFailed', new Error(errorDescription));
};
WebSocketClient.prototype.succeedHandshake = function() {
var connection = new WebSocketConnection(this.socket, [], this.protocol, true, this.config);
connection.webSocketVersion = this.config.webSocketVersion;
connection._addSocketEventListeners();
this.emit('connect', connection);
if (this.firstDataChunk.length > 0) {
connection.handleSocketData(this.firstDataChunk);
}
this.firstDataChunk = null;
};
WebSocketClient.prototype.abort = function() {
if (this._req) {
this._req.abort();
}
};
module.exports = WebSocketClient;

896
node_modules/websocket/lib/WebSocketConnection.js generated vendored Normal file
View File

@ -0,0 +1,896 @@
/************************************************************************
* Copyright 2010-2015 Brian McKelvey.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
var util = require('util');
var utils = require('./utils');
var EventEmitter = require('events').EventEmitter;
var WebSocketFrame = require('./WebSocketFrame');
var BufferList = require('../vendor/FastBufferList');
var Validation = require('./Validation').Validation;
var bufferAllocUnsafe = utils.bufferAllocUnsafe;
var bufferFromString = utils.bufferFromString;
// Connected, fully-open, ready to send and receive frames
const STATE_OPEN = 'open';
// Received a close frame from the remote peer
const STATE_PEER_REQUESTED_CLOSE = 'peer_requested_close';
// Sent close frame to remote peer. No further data can be sent.
const STATE_ENDING = 'ending';
// Connection is fully closed. No further data can be sent or received.
const STATE_CLOSED = 'closed';
var setImmediateImpl = ('setImmediate' in global) ?
global.setImmediate.bind(global) :
process.nextTick.bind(process);
var idCounter = 0;
function WebSocketConnection(socket, extensions, protocol, maskOutgoingPackets, config) {
this._debug = utils.BufferingLogger('websocket:connection', ++idCounter);
this._debug('constructor');
if (this._debug.enabled) {
instrumentSocketForDebugging(this, socket);
}
// Superclass Constructor
EventEmitter.call(this);
this._pingListenerCount = 0;
this.on('newListener', function(ev) {
if (ev === 'ping'){
this._pingListenerCount++;
}
}).on('removeListener', function(ev) {
if (ev === 'ping') {
this._pingListenerCount--;
}
});
this.config = config;
this.socket = socket;
this.protocol = protocol;
this.extensions = extensions;
this.remoteAddress = socket.remoteAddress;
this.closeReasonCode = -1;
this.closeDescription = null;
this.closeEventEmitted = false;
// We have to mask outgoing packets if we're acting as a WebSocket client.
this.maskOutgoingPackets = maskOutgoingPackets;
// We re-use the same buffers for the mask and frame header for all frames
// received on each connection to avoid a small memory allocation for each
// frame.
this.maskBytes = bufferAllocUnsafe(4);
this.frameHeader = bufferAllocUnsafe(10);
// the BufferList will handle the data streaming in
this.bufferList = new BufferList();
// Prepare for receiving first frame
this.currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
this.fragmentationSize = 0; // data received so far...
this.frameQueue = [];
// Various bits of connection state
this.connected = true;
this.state = STATE_OPEN;
this.waitingForCloseResponse = false;
// Received TCP FIN, socket's readable stream is finished.
this.receivedEnd = false;
this.closeTimeout = this.config.closeTimeout;
this.assembleFragments = this.config.assembleFragments;
this.maxReceivedMessageSize = this.config.maxReceivedMessageSize;
this.outputBufferFull = false;
this.inputPaused = false;
this.receivedDataHandler = this.processReceivedData.bind(this);
this._closeTimerHandler = this.handleCloseTimer.bind(this);
// Disable nagle algorithm?
this.socket.setNoDelay(this.config.disableNagleAlgorithm);
// Make sure there is no socket inactivity timeout
this.socket.setTimeout(0);
if (this.config.keepalive && !this.config.useNativeKeepalive) {
if (typeof(this.config.keepaliveInterval) !== 'number') {
throw new Error('keepaliveInterval must be specified and numeric ' +
'if keepalive is true.');
}
this._keepaliveTimerHandler = this.handleKeepaliveTimer.bind(this);
this.setKeepaliveTimer();
if (this.config.dropConnectionOnKeepaliveTimeout) {
if (typeof(this.config.keepaliveGracePeriod) !== 'number') {
throw new Error('keepaliveGracePeriod must be specified and ' +
'numeric if dropConnectionOnKeepaliveTimeout ' +
'is true.');
}
this._gracePeriodTimerHandler = this.handleGracePeriodTimer.bind(this);
}
}
else if (this.config.keepalive && this.config.useNativeKeepalive) {
if (!('setKeepAlive' in this.socket)) {
throw new Error('Unable to use native keepalive: unsupported by ' +
'this version of Node.');
}
this.socket.setKeepAlive(true, this.config.keepaliveInterval);
}
// The HTTP Client seems to subscribe to socket error events
// and re-dispatch them in such a way that doesn't make sense
// for users of our client, so we want to make sure nobody
// else is listening for error events on the socket besides us.
this.socket.removeAllListeners('error');
}
WebSocketConnection.CLOSE_REASON_NORMAL = 1000;
WebSocketConnection.CLOSE_REASON_GOING_AWAY = 1001;
WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR = 1002;
WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT = 1003;
WebSocketConnection.CLOSE_REASON_RESERVED = 1004; // Reserved value. Undefined meaning.
WebSocketConnection.CLOSE_REASON_NOT_PROVIDED = 1005; // Not to be used on the wire
WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006; // Not to be used on the wire
WebSocketConnection.CLOSE_REASON_INVALID_DATA = 1007;
WebSocketConnection.CLOSE_REASON_POLICY_VIOLATION = 1008;
WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG = 1009;
WebSocketConnection.CLOSE_REASON_EXTENSION_REQUIRED = 1010;
WebSocketConnection.CLOSE_REASON_INTERNAL_SERVER_ERROR = 1011;
WebSocketConnection.CLOSE_REASON_TLS_HANDSHAKE_FAILED = 1015; // Not to be used on the wire
WebSocketConnection.CLOSE_DESCRIPTIONS = {
1000: 'Normal connection closure',
1001: 'Remote peer is going away',
1002: 'Protocol error',
1003: 'Unprocessable input',
1004: 'Reserved',
1005: 'Reason not provided',
1006: 'Abnormal closure, no further detail available',
1007: 'Invalid data received',
1008: 'Policy violation',
1009: 'Message too big',
1010: 'Extension requested by client is required',
1011: 'Internal Server Error',
1015: 'TLS Handshake Failed'
};
function validateCloseReason(code) {
if (code < 1000) {
// Status codes in the range 0-999 are not used
return false;
}
if (code >= 1000 && code <= 2999) {
// Codes from 1000 - 2999 are reserved for use by the protocol. Only
// a few codes are defined, all others are currently illegal.
return [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014].indexOf(code) !== -1;
}
if (code >= 3000 && code <= 3999) {
// Reserved for use by libraries, frameworks, and applications.
// Should be registered with IANA. Interpretation of these codes is
// undefined by the WebSocket protocol.
return true;
}
if (code >= 4000 && code <= 4999) {
// Reserved for private use. Interpretation of these codes is
// undefined by the WebSocket protocol.
return true;
}
if (code >= 5000) {
return false;
}
}
util.inherits(WebSocketConnection, EventEmitter);
WebSocketConnection.prototype._addSocketEventListeners = function() {
this.socket.on('error', this.handleSocketError.bind(this));
this.socket.on('end', this.handleSocketEnd.bind(this));
this.socket.on('close', this.handleSocketClose.bind(this));
this.socket.on('drain', this.handleSocketDrain.bind(this));
this.socket.on('pause', this.handleSocketPause.bind(this));
this.socket.on('resume', this.handleSocketResume.bind(this));
this.socket.on('data', this.handleSocketData.bind(this));
};
// set or reset the keepalive timer when data is received.
WebSocketConnection.prototype.setKeepaliveTimer = function() {
this._debug('setKeepaliveTimer');
if (!this.config.keepalive || this.config.useNativeKeepalive) { return; }
this.clearKeepaliveTimer();
this.clearGracePeriodTimer();
this._keepaliveTimeoutID = setTimeout(this._keepaliveTimerHandler, this.config.keepaliveInterval);
};
WebSocketConnection.prototype.clearKeepaliveTimer = function() {
if (this._keepaliveTimeoutID) {
clearTimeout(this._keepaliveTimeoutID);
}
};
// No data has been received within config.keepaliveTimeout ms.
WebSocketConnection.prototype.handleKeepaliveTimer = function() {
this._debug('handleKeepaliveTimer');
this._keepaliveTimeoutID = null;
this.ping();
// If we are configured to drop connections if the client doesn't respond
// then set the grace period timer.
if (this.config.dropConnectionOnKeepaliveTimeout) {
this.setGracePeriodTimer();
}
else {
// Otherwise reset the keepalive timer to send the next ping.
this.setKeepaliveTimer();
}
};
WebSocketConnection.prototype.setGracePeriodTimer = function() {
this._debug('setGracePeriodTimer');
this.clearGracePeriodTimer();
this._gracePeriodTimeoutID = setTimeout(this._gracePeriodTimerHandler, this.config.keepaliveGracePeriod);
};
WebSocketConnection.prototype.clearGracePeriodTimer = function() {
if (this._gracePeriodTimeoutID) {
clearTimeout(this._gracePeriodTimeoutID);
}
};
WebSocketConnection.prototype.handleGracePeriodTimer = function() {
this._debug('handleGracePeriodTimer');
// If this is called, the client has not responded and is assumed dead.
this._gracePeriodTimeoutID = null;
this.drop(WebSocketConnection.CLOSE_REASON_ABNORMAL, 'Peer not responding.', true);
};
WebSocketConnection.prototype.handleSocketData = function(data) {
this._debug('handleSocketData');
// Reset the keepalive timer when receiving data of any kind.
this.setKeepaliveTimer();
// Add received data to our bufferList, which efficiently holds received
// data chunks in a linked list of Buffer objects.
this.bufferList.write(data);
this.processReceivedData();
};
WebSocketConnection.prototype.processReceivedData = function() {
this._debug('processReceivedData');
// If we're not connected, we should ignore any data remaining on the buffer.
if (!this.connected) { return; }
// Receiving/parsing is expected to be halted when paused.
if (this.inputPaused) { return; }
var frame = this.currentFrame;
// WebSocketFrame.prototype.addData returns true if all data necessary to
// parse the frame was available. It returns false if we are waiting for
// more data to come in on the wire.
if (!frame.addData(this.bufferList)) { this._debug('-- insufficient data for frame'); return; }
var self = this;
// Handle possible parsing errors
if (frame.protocolError) {
// Something bad happened.. get rid of this client.
this._debug('-- protocol error');
process.nextTick(function() {
self.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, frame.dropReason);
});
return;
}
else if (frame.frameTooLarge) {
this._debug('-- frame too large');
process.nextTick(function() {
self.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG, frame.dropReason);
});
return;
}
// For now since we don't support extensions, all RSV bits are illegal
if (frame.rsv1 || frame.rsv2 || frame.rsv3) {
this._debug('-- illegal rsv flag');
process.nextTick(function() {
self.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
'Unsupported usage of rsv bits without negotiated extension.');
});
return;
}
if (!this.assembleFragments) {
this._debug('-- emitting frame');
process.nextTick(function() { self.emit('frame', frame); });
}
process.nextTick(function() { self.processFrame(frame); });
this.currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
// If there's data remaining, schedule additional processing, but yield
// for now so that other connections have a chance to have their data
// processed. We use setImmediate here instead of process.nextTick to
// explicitly indicate that we wish for other I/O to be handled first.
if (this.bufferList.length > 0) {
setImmediateImpl(this.receivedDataHandler);
}
};
WebSocketConnection.prototype.handleSocketError = function(error) {
this._debug('handleSocketError: %j', error);
if (this.state === STATE_CLOSED) {
// See https://github.com/theturtle32/WebSocket-Node/issues/288
this._debug(' --- Socket \'error\' after \'close\'');
return;
}
this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL;
this.closeDescription = 'Socket Error: ' + error.syscall + ' ' + error.code;
this.connected = false;
this.state = STATE_CLOSED;
this.fragmentationSize = 0;
if (utils.eventEmitterListenerCount(this, 'error') > 0) {
this.emit('error', error);
}
this.socket.destroy(error);
this._debug.printOutput();
};
WebSocketConnection.prototype.handleSocketEnd = function() {
this._debug('handleSocketEnd: received socket end. state = %s', this.state);
this.receivedEnd = true;
if (this.state === STATE_CLOSED) {
// When using the TLS module, sometimes the socket will emit 'end'
// after it emits 'close'. I don't think that's correct behavior,
// but we should deal with it gracefully by ignoring it.
this._debug(' --- Socket \'end\' after \'close\'');
return;
}
if (this.state !== STATE_PEER_REQUESTED_CLOSE &&
this.state !== STATE_ENDING) {
this._debug(' --- UNEXPECTED socket end.');
this.socket.end();
}
};
WebSocketConnection.prototype.handleSocketClose = function(hadError) {
this._debug('handleSocketClose: received socket close');
this.socketHadError = hadError;
this.connected = false;
this.state = STATE_CLOSED;
// If closeReasonCode is still set to -1 at this point then we must
// not have received a close frame!!
if (this.closeReasonCode === -1) {
this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL;
this.closeDescription = 'Connection dropped by remote peer.';
}
this.clearCloseTimer();
this.clearKeepaliveTimer();
this.clearGracePeriodTimer();
if (!this.closeEventEmitted) {
this.closeEventEmitted = true;
this._debug('-- Emitting WebSocketConnection close event');
this.emit('close', this.closeReasonCode, this.closeDescription);
}
};
WebSocketConnection.prototype.handleSocketDrain = function() {
this._debug('handleSocketDrain: socket drain event');
this.outputBufferFull = false;
this.emit('drain');
};
WebSocketConnection.prototype.handleSocketPause = function() {
this._debug('handleSocketPause: socket pause event');
this.inputPaused = true;
this.emit('pause');
};
WebSocketConnection.prototype.handleSocketResume = function() {
this._debug('handleSocketResume: socket resume event');
this.inputPaused = false;
this.emit('resume');
this.processReceivedData();
};
WebSocketConnection.prototype.pause = function() {
this._debug('pause: pause requested');
this.socket.pause();
};
WebSocketConnection.prototype.resume = function() {
this._debug('resume: resume requested');
this.socket.resume();
};
WebSocketConnection.prototype.close = function(reasonCode, description) {
if (this.connected) {
this._debug('close: Initating clean WebSocket close sequence.');
if ('number' !== typeof reasonCode) {
reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
}
if (!validateCloseReason(reasonCode)) {
throw new Error('Close code ' + reasonCode + ' is not valid.');
}
if ('string' !== typeof description) {
description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode];
}
this.closeReasonCode = reasonCode;
this.closeDescription = description;
this.setCloseTimer();
this.sendCloseFrame(this.closeReasonCode, this.closeDescription);
this.state = STATE_ENDING;
this.connected = false;
}
};
WebSocketConnection.prototype.drop = function(reasonCode, description, skipCloseFrame) {
this._debug('drop');
if (typeof(reasonCode) !== 'number') {
reasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
}
if (typeof(description) !== 'string') {
// If no description is provided, try to look one up based on the
// specified reasonCode.
description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode];
}
this._debug('Forcefully dropping connection. skipCloseFrame: %s, code: %d, description: %s',
skipCloseFrame, reasonCode, description
);
this.closeReasonCode = reasonCode;
this.closeDescription = description;
this.frameQueue = [];
this.fragmentationSize = 0;
if (!skipCloseFrame) {
this.sendCloseFrame(reasonCode, description);
}
this.connected = false;
this.state = STATE_CLOSED;
this.clearCloseTimer();
this.clearKeepaliveTimer();
this.clearGracePeriodTimer();
if (!this.closeEventEmitted) {
this.closeEventEmitted = true;
this._debug('Emitting WebSocketConnection close event');
this.emit('close', this.closeReasonCode, this.closeDescription);
}
this._debug('Drop: destroying socket');
this.socket.destroy();
};
WebSocketConnection.prototype.setCloseTimer = function() {
this._debug('setCloseTimer');
this.clearCloseTimer();
this._debug('Setting close timer');
this.waitingForCloseResponse = true;
this.closeTimer = setTimeout(this._closeTimerHandler, this.closeTimeout);
};
WebSocketConnection.prototype.clearCloseTimer = function() {
this._debug('clearCloseTimer');
if (this.closeTimer) {
this._debug('Clearing close timer');
clearTimeout(this.closeTimer);
this.waitingForCloseResponse = false;
this.closeTimer = null;
}
};
WebSocketConnection.prototype.handleCloseTimer = function() {
this._debug('handleCloseTimer');
this.closeTimer = null;
if (this.waitingForCloseResponse) {
this._debug('Close response not received from client. Forcing socket end.');
this.waitingForCloseResponse = false;
this.state = STATE_CLOSED;
this.socket.end();
}
};
WebSocketConnection.prototype.processFrame = function(frame) {
this._debug('processFrame');
this._debug(' -- frame: %s', frame);
// Any non-control opcode besides 0x00 (continuation) received in the
// middle of a fragmented message is illegal.
if (this.frameQueue.length !== 0 && (frame.opcode > 0x00 && frame.opcode < 0x08)) {
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
'Illegal frame opcode 0x' + frame.opcode.toString(16) + ' ' +
'received in middle of fragmented message.');
return;
}
switch(frame.opcode) {
case 0x02: // WebSocketFrame.BINARY_FRAME
this._debug('-- Binary Frame');
if (this.assembleFragments) {
if (frame.fin) {
// Complete single-frame message received
this._debug('---- Emitting \'message\' event');
this.emit('message', {
type: 'binary',
binaryData: frame.binaryPayload
});
}
else {
// beginning of a fragmented message
this.frameQueue.push(frame);
this.fragmentationSize = frame.length;
}
}
break;
case 0x01: // WebSocketFrame.TEXT_FRAME
this._debug('-- Text Frame');
if (this.assembleFragments) {
if (frame.fin) {
if (!Validation.isValidUTF8(frame.binaryPayload)) {
this.drop(WebSocketConnection.CLOSE_REASON_INVALID_DATA,
'Invalid UTF-8 Data Received');
return;
}
// Complete single-frame message received
this._debug('---- Emitting \'message\' event');
this.emit('message', {
type: 'utf8',
utf8Data: frame.binaryPayload.toString('utf8')
});
}
else {
// beginning of a fragmented message
this.frameQueue.push(frame);
this.fragmentationSize = frame.length;
}
}
break;
case 0x00: // WebSocketFrame.CONTINUATION
this._debug('-- Continuation Frame');
if (this.assembleFragments) {
if (this.frameQueue.length === 0) {
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
'Unexpected Continuation Frame');
return;
}
this.fragmentationSize += frame.length;
if (this.fragmentationSize > this.maxReceivedMessageSize) {
this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG,
'Maximum message size exceeded.');
return;
}
this.frameQueue.push(frame);
if (frame.fin) {
// end of fragmented message, so we process the whole
// message now. We also have to decode the utf-8 data
// for text frames after combining all the fragments.
var bytesCopied = 0;
var binaryPayload = bufferAllocUnsafe(this.fragmentationSize);
var opcode = this.frameQueue[0].opcode;
this.frameQueue.forEach(function (currentFrame) {
currentFrame.binaryPayload.copy(binaryPayload, bytesCopied);
bytesCopied += currentFrame.binaryPayload.length;
});
this.frameQueue = [];
this.fragmentationSize = 0;
switch (opcode) {
case 0x02: // WebSocketOpcode.BINARY_FRAME
this.emit('message', {
type: 'binary',
binaryData: binaryPayload
});
break;
case 0x01: // WebSocketOpcode.TEXT_FRAME
if (!Validation.isValidUTF8(binaryPayload)) {
this.drop(WebSocketConnection.CLOSE_REASON_INVALID_DATA,
'Invalid UTF-8 Data Received');
return;
}
this.emit('message', {
type: 'utf8',
utf8Data: binaryPayload.toString('utf8')
});
break;
default:
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
'Unexpected first opcode in fragmentation sequence: 0x' + opcode.toString(16));
return;
}
}
}
break;
case 0x09: // WebSocketFrame.PING
this._debug('-- Ping Frame');
if (this._pingListenerCount > 0) {
// logic to emit the ping frame: this is only done when a listener is known to exist
// Expose a function allowing the user to override the default ping() behavior
var cancelled = false;
var cancel = function() {
cancelled = true;
};
this.emit('ping', cancel, frame.binaryPayload);
// Only send a pong if the client did not indicate that he would like to cancel
if (!cancelled) {
this.pong(frame.binaryPayload);
}
}
else {
this.pong(frame.binaryPayload);
}
break;
case 0x0A: // WebSocketFrame.PONG
this._debug('-- Pong Frame');
this.emit('pong', frame.binaryPayload);
break;
case 0x08: // WebSocketFrame.CONNECTION_CLOSE
this._debug('-- Close Frame');
if (this.waitingForCloseResponse) {
// Got response to our request to close the connection.
// Close is complete, so we just hang up.
this._debug('---- Got close response from peer. Completing closing handshake.');
this.clearCloseTimer();
this.waitingForCloseResponse = false;
this.state = STATE_CLOSED;
this.socket.end();
return;
}
this._debug('---- Closing handshake initiated by peer.');
// Got request from other party to close connection.
// Send back acknowledgement and then hang up.
this.state = STATE_PEER_REQUESTED_CLOSE;
var respondCloseReasonCode;
// Make sure the close reason provided is legal according to
// the protocol spec. Providing no close status is legal.
// WebSocketFrame sets closeStatus to -1 by default, so if it
// is still -1, then no status was provided.
if (frame.invalidCloseFrameLength) {
this.closeReasonCode = 1005; // 1005 = No reason provided.
respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
}
else if (frame.closeStatus === -1 || validateCloseReason(frame.closeStatus)) {
this.closeReasonCode = frame.closeStatus;
respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
}
else {
this.closeReasonCode = frame.closeStatus;
respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
}
// If there is a textual description in the close frame, extract it.
if (frame.binaryPayload.length > 1) {
if (!Validation.isValidUTF8(frame.binaryPayload)) {
this.drop(WebSocketConnection.CLOSE_REASON_INVALID_DATA,
'Invalid UTF-8 Data Received');
return;
}
this.closeDescription = frame.binaryPayload.toString('utf8');
}
else {
this.closeDescription = WebSocketConnection.CLOSE_DESCRIPTIONS[this.closeReasonCode];
}
this._debug(
'------ Remote peer %s - code: %d - %s - close frame payload length: %d',
this.remoteAddress, this.closeReasonCode,
this.closeDescription, frame.length
);
this._debug('------ responding to remote peer\'s close request.');
this.sendCloseFrame(respondCloseReasonCode, null);
this.connected = false;
break;
default:
this._debug('-- Unrecognized Opcode %d', frame.opcode);
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
'Unrecognized Opcode: 0x' + frame.opcode.toString(16));
break;
}
};
WebSocketConnection.prototype.send = function(data, cb) {
this._debug('send');
if (Buffer.isBuffer(data)) {
this.sendBytes(data, cb);
}
else if (typeof(data['toString']) === 'function') {
this.sendUTF(data, cb);
}
else {
throw new Error('Data provided must either be a Node Buffer or implement toString()');
}
};
WebSocketConnection.prototype.sendUTF = function(data, cb) {
data = bufferFromString(data.toString(), 'utf8');
this._debug('sendUTF: %d bytes', data.length);
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.opcode = 0x01; // WebSocketOpcode.TEXT_FRAME
frame.binaryPayload = data;
this.fragmentAndSend(frame, cb);
};
WebSocketConnection.prototype.sendBytes = function(data, cb) {
this._debug('sendBytes');
if (!Buffer.isBuffer(data)) {
throw new Error('You must pass a Node Buffer object to WebSocketConnection.prototype.sendBytes()');
}
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.opcode = 0x02; // WebSocketOpcode.BINARY_FRAME
frame.binaryPayload = data;
this.fragmentAndSend(frame, cb);
};
WebSocketConnection.prototype.ping = function(data) {
this._debug('ping');
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.opcode = 0x09; // WebSocketOpcode.PING
frame.fin = true;
if (data) {
if (!Buffer.isBuffer(data)) {
data = bufferFromString(data.toString(), 'utf8');
}
if (data.length > 125) {
this._debug('WebSocket: Data for ping is longer than 125 bytes. Truncating.');
data = data.slice(0,124);
}
frame.binaryPayload = data;
}
this.sendFrame(frame);
};
// Pong frames have to echo back the contents of the data portion of the
// ping frame exactly, byte for byte.
WebSocketConnection.prototype.pong = function(binaryPayload) {
this._debug('pong');
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.opcode = 0x0A; // WebSocketOpcode.PONG
if (Buffer.isBuffer(binaryPayload) && binaryPayload.length > 125) {
this._debug('WebSocket: Data for pong is longer than 125 bytes. Truncating.');
binaryPayload = binaryPayload.slice(0,124);
}
frame.binaryPayload = binaryPayload;
frame.fin = true;
this.sendFrame(frame);
};
WebSocketConnection.prototype.fragmentAndSend = function(frame, cb) {
this._debug('fragmentAndSend');
if (frame.opcode > 0x07) {
throw new Error('You cannot fragment control frames.');
}
var threshold = this.config.fragmentationThreshold;
var length = frame.binaryPayload.length;
// Send immediately if fragmentation is disabled or the message is not
// larger than the fragmentation threshold.
if (!this.config.fragmentOutgoingMessages || (frame.binaryPayload && length <= threshold)) {
frame.fin = true;
this.sendFrame(frame, cb);
return;
}
var numFragments = Math.ceil(length / threshold);
var sentFragments = 0;
var sentCallback = function fragmentSentCallback(err) {
if (err) {
if (typeof cb === 'function') {
// pass only the first error
cb(err);
cb = null;
}
return;
}
++sentFragments;
if ((sentFragments === numFragments) && (typeof cb === 'function')) {
cb();
}
};
for (var i=1; i <= numFragments; i++) {
var currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
// continuation opcode except for first frame.
currentFrame.opcode = (i === 1) ? frame.opcode : 0x00;
// fin set on last frame only
currentFrame.fin = (i === numFragments);
// length is likely to be shorter on the last fragment
var currentLength = (i === numFragments) ? length - (threshold * (i-1)) : threshold;
var sliceStart = threshold * (i-1);
// Slice the right portion of the original payload
currentFrame.binaryPayload = frame.binaryPayload.slice(sliceStart, sliceStart + currentLength);
this.sendFrame(currentFrame, sentCallback);
}
};
WebSocketConnection.prototype.sendCloseFrame = function(reasonCode, description, cb) {
if (typeof(reasonCode) !== 'number') {
reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
}
this._debug('sendCloseFrame state: %s, reasonCode: %d, description: %s', this.state, reasonCode, description);
if (this.state !== STATE_OPEN && this.state !== STATE_PEER_REQUESTED_CLOSE) { return; }
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.fin = true;
frame.opcode = 0x08; // WebSocketOpcode.CONNECTION_CLOSE
frame.closeStatus = reasonCode;
if (typeof(description) === 'string') {
frame.binaryPayload = bufferFromString(description, 'utf8');
}
this.sendFrame(frame, cb);
this.socket.end();
};
WebSocketConnection.prototype.sendFrame = function(frame, cb) {
this._debug('sendFrame');
frame.mask = this.maskOutgoingPackets;
var flushed = this.socket.write(frame.toBuffer(), cb);
this.outputBufferFull = !flushed;
return flushed;
};
module.exports = WebSocketConnection;
function instrumentSocketForDebugging(connection, socket) {
/* jshint loopfunc: true */
if (!connection._debug.enabled) { return; }
var originalSocketEmit = socket.emit;
socket.emit = function(event) {
connection._debug('||| Socket Event \'%s\'', event);
originalSocketEmit.apply(this, arguments);
};
for (var key in socket) {
if ('function' !== typeof(socket[key])) { continue; }
if (['emit'].indexOf(key) !== -1) { continue; }
(function(key) {
var original = socket[key];
if (key === 'on') {
socket[key] = function proxyMethod__EventEmitter__On() {
connection._debug('||| Socket method called: %s (%s)', key, arguments[0]);
return original.apply(this, arguments);
};
return;
}
socket[key] = function proxyMethod() {
connection._debug('||| Socket method called: %s', key);
return original.apply(this, arguments);
};
})(key);
}
}

280
node_modules/websocket/lib/WebSocketFrame.js generated vendored Normal file
View File

@ -0,0 +1,280 @@
/************************************************************************
* Copyright 2010-2015 Brian McKelvey.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
var bufferUtil = require('./BufferUtil').BufferUtil;
var bufferAllocUnsafe = require('./utils').bufferAllocUnsafe;
const DECODE_HEADER = 1;
const WAITING_FOR_16_BIT_LENGTH = 2;
const WAITING_FOR_64_BIT_LENGTH = 3;
const WAITING_FOR_MASK_KEY = 4;
const WAITING_FOR_PAYLOAD = 5;
const COMPLETE = 6;
// WebSocketConnection will pass shared buffer objects for maskBytes and
// frameHeader into the constructor to avoid tons of small memory allocations
// for each frame we have to parse. This is only used for parsing frames
// we receive off the wire.
function WebSocketFrame(maskBytes, frameHeader, config) {
this.maskBytes = maskBytes;
this.frameHeader = frameHeader;
this.config = config;
this.maxReceivedFrameSize = config.maxReceivedFrameSize;
this.protocolError = false;
this.frameTooLarge = false;
this.invalidCloseFrameLength = false;
this.parseState = DECODE_HEADER;
this.closeStatus = -1;
}
WebSocketFrame.prototype.addData = function(bufferList) {
if (this.parseState === DECODE_HEADER) {
if (bufferList.length >= 2) {
bufferList.joinInto(this.frameHeader, 0, 0, 2);
bufferList.advance(2);
var firstByte = this.frameHeader[0];
var secondByte = this.frameHeader[1];
this.fin = Boolean(firstByte & 0x80);
this.rsv1 = Boolean(firstByte & 0x40);
this.rsv2 = Boolean(firstByte & 0x20);
this.rsv3 = Boolean(firstByte & 0x10);
this.mask = Boolean(secondByte & 0x80);
this.opcode = firstByte & 0x0F;
this.length = secondByte & 0x7F;
// Control frame sanity check
if (this.opcode >= 0x08) {
if (this.length > 125) {
this.protocolError = true;
this.dropReason = 'Illegal control frame longer than 125 bytes.';
return true;
}
if (!this.fin) {
this.protocolError = true;
this.dropReason = 'Control frames must not be fragmented.';
return true;
}
}
if (this.length === 126) {
this.parseState = WAITING_FOR_16_BIT_LENGTH;
}
else if (this.length === 127) {
this.parseState = WAITING_FOR_64_BIT_LENGTH;
}
else {
this.parseState = WAITING_FOR_MASK_KEY;
}
}
}
if (this.parseState === WAITING_FOR_16_BIT_LENGTH) {
if (bufferList.length >= 2) {
bufferList.joinInto(this.frameHeader, 2, 0, 2);
bufferList.advance(2);
this.length = this.frameHeader.readUInt16BE(2);
this.parseState = WAITING_FOR_MASK_KEY;
}
}
else if (this.parseState === WAITING_FOR_64_BIT_LENGTH) {
if (bufferList.length >= 8) {
bufferList.joinInto(this.frameHeader, 2, 0, 8);
bufferList.advance(8);
var lengthPair = [
this.frameHeader.readUInt32BE(2),
this.frameHeader.readUInt32BE(2+4)
];
if (lengthPair[0] !== 0) {
this.protocolError = true;
this.dropReason = 'Unsupported 64-bit length frame received';
return true;
}
this.length = lengthPair[1];
this.parseState = WAITING_FOR_MASK_KEY;
}
}
if (this.parseState === WAITING_FOR_MASK_KEY) {
if (this.mask) {
if (bufferList.length >= 4) {
bufferList.joinInto(this.maskBytes, 0, 0, 4);
bufferList.advance(4);
this.parseState = WAITING_FOR_PAYLOAD;
}
}
else {
this.parseState = WAITING_FOR_PAYLOAD;
}
}
if (this.parseState === WAITING_FOR_PAYLOAD) {
if (this.length > this.maxReceivedFrameSize) {
this.frameTooLarge = true;
this.dropReason = 'Frame size of ' + this.length.toString(10) +
' bytes exceeds maximum accepted frame size';
return true;
}
if (this.length === 0) {
this.binaryPayload = bufferAllocUnsafe(0);
this.parseState = COMPLETE;
return true;
}
if (bufferList.length >= this.length) {
this.binaryPayload = bufferList.take(this.length);
bufferList.advance(this.length);
if (this.mask) {
bufferUtil.unmask(this.binaryPayload, this.maskBytes);
// xor(this.binaryPayload, this.maskBytes, 0);
}
if (this.opcode === 0x08) { // WebSocketOpcode.CONNECTION_CLOSE
if (this.length === 1) {
// Invalid length for a close frame. Must be zero or at least two.
this.binaryPayload = bufferAllocUnsafe(0);
this.invalidCloseFrameLength = true;
}
if (this.length >= 2) {
this.closeStatus = this.binaryPayload.readUInt16BE(0);
this.binaryPayload = this.binaryPayload.slice(2);
}
}
this.parseState = COMPLETE;
return true;
}
}
return false;
};
WebSocketFrame.prototype.throwAwayPayload = function(bufferList) {
if (bufferList.length >= this.length) {
bufferList.advance(this.length);
this.parseState = COMPLETE;
return true;
}
return false;
};
WebSocketFrame.prototype.toBuffer = function(nullMask) {
var maskKey;
var headerLength = 2;
var data;
var outputPos;
var firstByte = 0x00;
var secondByte = 0x00;
if (this.fin) {
firstByte |= 0x80;
}
if (this.rsv1) {
firstByte |= 0x40;
}
if (this.rsv2) {
firstByte |= 0x20;
}
if (this.rsv3) {
firstByte |= 0x10;
}
if (this.mask) {
secondByte |= 0x80;
}
firstByte |= (this.opcode & 0x0F);
// the close frame is a special case because the close reason is
// prepended to the payload data.
if (this.opcode === 0x08) {
this.length = 2;
if (this.binaryPayload) {
this.length += this.binaryPayload.length;
}
data = bufferAllocUnsafe(this.length);
data.writeUInt16BE(this.closeStatus, 0);
if (this.length > 2) {
this.binaryPayload.copy(data, 2);
}
}
else if (this.binaryPayload) {
data = this.binaryPayload;
this.length = data.length;
}
else {
this.length = 0;
}
if (this.length <= 125) {
// encode the length directly into the two-byte frame header
secondByte |= (this.length & 0x7F);
}
else if (this.length > 125 && this.length <= 0xFFFF) {
// Use 16-bit length
secondByte |= 126;
headerLength += 2;
}
else if (this.length > 0xFFFF) {
// Use 64-bit length
secondByte |= 127;
headerLength += 8;
}
var output = bufferAllocUnsafe(this.length + headerLength + (this.mask ? 4 : 0));
// write the frame header
output[0] = firstByte;
output[1] = secondByte;
outputPos = 2;
if (this.length > 125 && this.length <= 0xFFFF) {
// write 16-bit length
output.writeUInt16BE(this.length, outputPos);
outputPos += 2;
}
else if (this.length > 0xFFFF) {
// write 64-bit length
output.writeUInt32BE(0x00000000, outputPos);
output.writeUInt32BE(this.length, outputPos + 4);
outputPos += 8;
}
if (this.mask) {
maskKey = nullMask ? 0 : ((Math.random() * 0xFFFFFFFF) >>> 0);
this.maskBytes.writeUInt32BE(maskKey, 0);
// write the mask key
this.maskBytes.copy(output, outputPos);
outputPos += 4;
if (data) {
bufferUtil.mask(data, this.maskBytes, output, outputPos, this.length);
}
}
else if (data) {
data.copy(output, outputPos);
}
return output;
};
WebSocketFrame.prototype.toString = function() {
return 'Opcode: ' + this.opcode + ', fin: ' + this.fin + ', length: ' + this.length + ', hasPayload: ' + Boolean(this.binaryPayload) + ', masked: ' + this.mask;
};
module.exports = WebSocketFrame;

524
node_modules/websocket/lib/WebSocketRequest.js generated vendored Normal file
View File

@ -0,0 +1,524 @@
/************************************************************************
* Copyright 2010-2015 Brian McKelvey.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
var crypto = require('crypto');
var util = require('util');
var url = require('url');
var EventEmitter = require('events').EventEmitter;
var WebSocketConnection = require('./WebSocketConnection');
var headerValueSplitRegExp = /,\s*/;
var headerParamSplitRegExp = /;\s*/;
var headerSanitizeRegExp = /[\r\n]/g;
var xForwardedForSeparatorRegExp = /,\s*/;
var separators = [
'(', ')', '<', '>', '@',
',', ';', ':', '\\', '\"',
'/', '[', ']', '?', '=',
'{', '}', ' ', String.fromCharCode(9)
];
var controlChars = [String.fromCharCode(127) /* DEL */];
for (var i=0; i < 31; i ++) {
/* US-ASCII Control Characters */
controlChars.push(String.fromCharCode(i));
}
var cookieNameValidateRegEx = /([\x00-\x20\x22\x28\x29\x2c\x2f\x3a-\x3f\x40\x5b-\x5e\x7b\x7d\x7f])/;
var cookieValueValidateRegEx = /[^\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]/;
var cookieValueDQuoteValidateRegEx = /^"[^"]*"$/;
var controlCharsAndSemicolonRegEx = /[\x00-\x20\x3b]/g;
var cookieSeparatorRegEx = /[;,] */;
var httpStatusDescriptions = {
100: 'Continue',
101: 'Switching Protocols',
200: 'OK',
201: 'Created',
203: 'Non-Authoritative Information',
204: 'No Content',
205: 'Reset Content',
206: 'Partial Content',
300: 'Multiple Choices',
301: 'Moved Permanently',
302: 'Found',
303: 'See Other',
304: 'Not Modified',
305: 'Use Proxy',
307: 'Temporary Redirect',
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
406: 'Not Acceptable',
407: 'Proxy Authorization Required',
408: 'Request Timeout',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Request Entity Too Long',
414: 'Request-URI Too Long',
415: 'Unsupported Media Type',
416: 'Requested Range Not Satisfiable',
417: 'Expectation Failed',
426: 'Upgrade Required',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Timeout',
505: 'HTTP Version Not Supported'
};
function WebSocketRequest(socket, httpRequest, serverConfig) {
// Superclass Constructor
EventEmitter.call(this);
this.socket = socket;
this.httpRequest = httpRequest;
this.resource = httpRequest.url;
this.remoteAddress = socket.remoteAddress;
this.remoteAddresses = [this.remoteAddress];
this.serverConfig = serverConfig;
// Watch for the underlying TCP socket closing before we call accept
this._socketIsClosing = false;
this._socketCloseHandler = this._handleSocketCloseBeforeAccept.bind(this);
this.socket.on('end', this._socketCloseHandler);
this.socket.on('close', this._socketCloseHandler);
this._resolved = false;
}
util.inherits(WebSocketRequest, EventEmitter);
WebSocketRequest.prototype.readHandshake = function() {
var self = this;
var request = this.httpRequest;
// Decode URL
this.resourceURL = url.parse(this.resource, true);
this.host = request.headers['host'];
if (!this.host) {
throw new Error('Client must provide a Host header.');
}
this.key = request.headers['sec-websocket-key'];
if (!this.key) {
throw new Error('Client must provide a value for Sec-WebSocket-Key.');
}
this.webSocketVersion = parseInt(request.headers['sec-websocket-version'], 10);
if (!this.webSocketVersion || isNaN(this.webSocketVersion)) {
throw new Error('Client must provide a value for Sec-WebSocket-Version.');
}
switch (this.webSocketVersion) {
case 8:
case 13:
break;
default:
var e = new Error('Unsupported websocket client version: ' + this.webSocketVersion +
'Only versions 8 and 13 are supported.');
e.httpCode = 426;
e.headers = {
'Sec-WebSocket-Version': '13'
};
throw e;
}
if (this.webSocketVersion === 13) {
this.origin = request.headers['origin'];
}
else if (this.webSocketVersion === 8) {
this.origin = request.headers['sec-websocket-origin'];
}
// Protocol is optional.
var protocolString = request.headers['sec-websocket-protocol'];
this.protocolFullCaseMap = {};
this.requestedProtocols = [];
if (protocolString) {
var requestedProtocolsFullCase = protocolString.split(headerValueSplitRegExp);
requestedProtocolsFullCase.forEach(function(protocol) {
var lcProtocol = protocol.toLocaleLowerCase();
self.requestedProtocols.push(lcProtocol);
self.protocolFullCaseMap[lcProtocol] = protocol;
});
}
if (!this.serverConfig.ignoreXForwardedFor &&
request.headers['x-forwarded-for']) {
var immediatePeerIP = this.remoteAddress;
this.remoteAddresses = request.headers['x-forwarded-for']
.split(xForwardedForSeparatorRegExp);
this.remoteAddresses.push(immediatePeerIP);
this.remoteAddress = this.remoteAddresses[0];
}
// Extensions are optional.
var extensionsString = request.headers['sec-websocket-extensions'];
this.requestedExtensions = this.parseExtensions(extensionsString);
// Cookies are optional
var cookieString = request.headers['cookie'];
this.cookies = this.parseCookies(cookieString);
};
WebSocketRequest.prototype.parseExtensions = function(extensionsString) {
if (!extensionsString || extensionsString.length === 0) {
return [];
}
var extensions = extensionsString.toLocaleLowerCase().split(headerValueSplitRegExp);
extensions.forEach(function(extension, index, array) {
var params = extension.split(headerParamSplitRegExp);
var extensionName = params[0];
var extensionParams = params.slice(1);
extensionParams.forEach(function(rawParam, index, array) {
var arr = rawParam.split('=');
var obj = {
name: arr[0],
value: arr[1]
};
array.splice(index, 1, obj);
});
var obj = {
name: extensionName,
params: extensionParams
};
array.splice(index, 1, obj);
});
return extensions;
};
// This function adapted from node-cookie
// https://github.com/shtylman/node-cookie
WebSocketRequest.prototype.parseCookies = function(str) {
// Sanity Check
if (!str || typeof(str) !== 'string') {
return [];
}
var cookies = [];
var pairs = str.split(cookieSeparatorRegEx);
pairs.forEach(function(pair) {
var eq_idx = pair.indexOf('=');
if (eq_idx === -1) {
cookies.push({
name: pair,
value: null
});
return;
}
var key = pair.substr(0, eq_idx).trim();
var val = pair.substr(++eq_idx, pair.length).trim();
// quoted values
if ('"' === val[0]) {
val = val.slice(1, -1);
}
cookies.push({
name: key,
value: decodeURIComponent(val)
});
});
return cookies;
};
WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, cookies) {
this._verifyResolution();
// TODO: Handle extensions
var protocolFullCase;
if (acceptedProtocol) {
protocolFullCase = this.protocolFullCaseMap[acceptedProtocol.toLocaleLowerCase()];
if (typeof(protocolFullCase) === 'undefined') {
protocolFullCase = acceptedProtocol;
}
}
else {
protocolFullCase = acceptedProtocol;
}
this.protocolFullCaseMap = null;
// Create key validation hash
var sha1 = crypto.createHash('sha1');
sha1.update(this.key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11');
var acceptKey = sha1.digest('base64');
var response = 'HTTP/1.1 101 Switching Protocols\r\n' +
'Upgrade: websocket\r\n' +
'Connection: Upgrade\r\n' +
'Sec-WebSocket-Accept: ' + acceptKey + '\r\n';
if (protocolFullCase) {
// validate protocol
for (var i=0; i < protocolFullCase.length; i++) {
var charCode = protocolFullCase.charCodeAt(i);
var character = protocolFullCase.charAt(i);
if (charCode < 0x21 || charCode > 0x7E || separators.indexOf(character) !== -1) {
this.reject(500);
throw new Error('Illegal character "' + String.fromCharCode(character) + '" in subprotocol.');
}
}
if (this.requestedProtocols.indexOf(acceptedProtocol) === -1) {
this.reject(500);
throw new Error('Specified protocol was not requested by the client.');
}
protocolFullCase = protocolFullCase.replace(headerSanitizeRegExp, '');
response += 'Sec-WebSocket-Protocol: ' + protocolFullCase + '\r\n';
}
this.requestedProtocols = null;
if (allowedOrigin) {
allowedOrigin = allowedOrigin.replace(headerSanitizeRegExp, '');
if (this.webSocketVersion === 13) {
response += 'Origin: ' + allowedOrigin + '\r\n';
}
else if (this.webSocketVersion === 8) {
response += 'Sec-WebSocket-Origin: ' + allowedOrigin + '\r\n';
}
}
if (cookies) {
if (!Array.isArray(cookies)) {
this.reject(500);
throw new Error('Value supplied for "cookies" argument must be an array.');
}
var seenCookies = {};
cookies.forEach(function(cookie) {
if (!cookie.name || !cookie.value) {
this.reject(500);
throw new Error('Each cookie to set must at least provide a "name" and "value"');
}
// Make sure there are no \r\n sequences inserted
cookie.name = cookie.name.replace(controlCharsAndSemicolonRegEx, '');
cookie.value = cookie.value.replace(controlCharsAndSemicolonRegEx, '');
if (seenCookies[cookie.name]) {
this.reject(500);
throw new Error('You may not specify the same cookie name twice.');
}
seenCookies[cookie.name] = true;
// token (RFC 2616, Section 2.2)
var invalidChar = cookie.name.match(cookieNameValidateRegEx);
if (invalidChar) {
this.reject(500);
throw new Error('Illegal character ' + invalidChar[0] + ' in cookie name');
}
// RFC 6265, Section 4.1.1
// *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) | %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
if (cookie.value.match(cookieValueDQuoteValidateRegEx)) {
invalidChar = cookie.value.slice(1, -1).match(cookieValueValidateRegEx);
} else {
invalidChar = cookie.value.match(cookieValueValidateRegEx);
}
if (invalidChar) {
this.reject(500);
throw new Error('Illegal character ' + invalidChar[0] + ' in cookie value');
}
var cookieParts = [cookie.name + '=' + cookie.value];
// RFC 6265, Section 4.1.1
// 'Path=' path-value | <any CHAR except CTLs or ';'>
if(cookie.path){
invalidChar = cookie.path.match(controlCharsAndSemicolonRegEx);
if (invalidChar) {
this.reject(500);
throw new Error('Illegal character ' + invalidChar[0] + ' in cookie path');
}
cookieParts.push('Path=' + cookie.path);
}
// RFC 6265, Section 4.1.2.3
// 'Domain=' subdomain
if (cookie.domain) {
if (typeof(cookie.domain) !== 'string') {
this.reject(500);
throw new Error('Domain must be specified and must be a string.');
}
invalidChar = cookie.domain.match(controlCharsAndSemicolonRegEx);
if (invalidChar) {
this.reject(500);
throw new Error('Illegal character ' + invalidChar[0] + ' in cookie domain');
}
cookieParts.push('Domain=' + cookie.domain.toLowerCase());
}
// RFC 6265, Section 4.1.1
//'Expires=' sane-cookie-date | Force Date object requirement by using only epoch
if (cookie.expires) {
if (!(cookie.expires instanceof Date)){
this.reject(500);
throw new Error('Value supplied for cookie "expires" must be a vaild date object');
}
cookieParts.push('Expires=' + cookie.expires.toGMTString());
}
// RFC 6265, Section 4.1.1
//'Max-Age=' non-zero-digit *DIGIT
if (cookie.maxage) {
var maxage = cookie.maxage;
if (typeof(maxage) === 'string') {
maxage = parseInt(maxage, 10);
}
if (isNaN(maxage) || maxage <= 0 ) {
this.reject(500);
throw new Error('Value supplied for cookie "maxage" must be a non-zero number');
}
maxage = Math.round(maxage);
cookieParts.push('Max-Age=' + maxage.toString(10));
}
// RFC 6265, Section 4.1.1
//'Secure;'
if (cookie.secure) {
if (typeof(cookie.secure) !== 'boolean') {
this.reject(500);
throw new Error('Value supplied for cookie "secure" must be of type boolean');
}
cookieParts.push('Secure');
}
// RFC 6265, Section 4.1.1
//'HttpOnly;'
if (cookie.httponly) {
if (typeof(cookie.httponly) !== 'boolean') {
this.reject(500);
throw new Error('Value supplied for cookie "httponly" must be of type boolean');
}
cookieParts.push('HttpOnly');
}
response += ('Set-Cookie: ' + cookieParts.join(';') + '\r\n');
}.bind(this));
}
// TODO: handle negotiated extensions
// if (negotiatedExtensions) {
// response += 'Sec-WebSocket-Extensions: ' + negotiatedExtensions.join(', ') + '\r\n';
// }
// Mark the request resolved now so that the user can't call accept or
// reject a second time.
this._resolved = true;
this.emit('requestResolved', this);
response += '\r\n';
var connection = new WebSocketConnection(this.socket, [], acceptedProtocol, false, this.serverConfig);
connection.webSocketVersion = this.webSocketVersion;
connection.remoteAddress = this.remoteAddress;
connection.remoteAddresses = this.remoteAddresses;
var self = this;
if (this._socketIsClosing) {
// Handle case when the client hangs up before we get a chance to
// accept the connection and send our side of the opening handshake.
cleanupFailedConnection(connection);
}
else {
this.socket.write(response, 'ascii', function(error) {
if (error) {
cleanupFailedConnection(connection);
return;
}
self._removeSocketCloseListeners();
connection._addSocketEventListeners();
});
}
this.emit('requestAccepted', connection);
return connection;
};
WebSocketRequest.prototype.reject = function(status, reason, extraHeaders) {
this._verifyResolution();
// Mark the request resolved now so that the user can't call accept or
// reject a second time.
this._resolved = true;
this.emit('requestResolved', this);
if (typeof(status) !== 'number') {
status = 403;
}
var response = 'HTTP/1.1 ' + status + ' ' + httpStatusDescriptions[status] + '\r\n' +
'Connection: close\r\n';
if (reason) {
reason = reason.replace(headerSanitizeRegExp, '');
response += 'X-WebSocket-Reject-Reason: ' + reason + '\r\n';
}
if (extraHeaders) {
for (var key in extraHeaders) {
var sanitizedValue = extraHeaders[key].toString().replace(headerSanitizeRegExp, '');
var sanitizedKey = key.replace(headerSanitizeRegExp, '');
response += (sanitizedKey + ': ' + sanitizedValue + '\r\n');
}
}
response += '\r\n';
this.socket.end(response, 'ascii');
this.emit('requestRejected', this);
};
WebSocketRequest.prototype._handleSocketCloseBeforeAccept = function() {
this._socketIsClosing = true;
this._removeSocketCloseListeners();
};
WebSocketRequest.prototype._removeSocketCloseListeners = function() {
this.socket.removeListener('end', this._socketCloseHandler);
this.socket.removeListener('close', this._socketCloseHandler);
};
WebSocketRequest.prototype._verifyResolution = function() {
if (this._resolved) {
throw new Error('WebSocketRequest may only be accepted or rejected one time.');
}
};
function cleanupFailedConnection(connection) {
// Since we have to return a connection object even if the socket is
// already dead in order not to break the API, we schedule a 'close'
// event on the connection object to occur immediately.
process.nextTick(function() {
// WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006
// Third param: Skip sending the close frame to a dead socket
connection.drop(1006, 'TCP connection lost before handshake completed.', true);
});
}
module.exports = WebSocketRequest;

157
node_modules/websocket/lib/WebSocketRouter.js generated vendored Normal file
View File

@ -0,0 +1,157 @@
/************************************************************************
* Copyright 2010-2015 Brian McKelvey.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
var extend = require('./utils').extend;
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var WebSocketRouterRequest = require('./WebSocketRouterRequest');
function WebSocketRouter(config) {
// Superclass Constructor
EventEmitter.call(this);
this.config = {
// The WebSocketServer instance to attach to.
server: null
};
if (config) {
extend(this.config, config);
}
this.handlers = [];
this._requestHandler = this.handleRequest.bind(this);
if (this.config.server) {
this.attachServer(this.config.server);
}
}
util.inherits(WebSocketRouter, EventEmitter);
WebSocketRouter.prototype.attachServer = function(server) {
if (server) {
this.server = server;
this.server.on('request', this._requestHandler);
}
else {
throw new Error('You must specify a WebSocketServer instance to attach to.');
}
};
WebSocketRouter.prototype.detachServer = function() {
if (this.server) {
this.server.removeListener('request', this._requestHandler);
this.server = null;
}
else {
throw new Error('Cannot detach from server: not attached.');
}
};
WebSocketRouter.prototype.mount = function(path, protocol, callback) {
if (!path) {
throw new Error('You must specify a path for this handler.');
}
if (!protocol) {
protocol = '____no_protocol____';
}
if (!callback) {
throw new Error('You must specify a callback for this handler.');
}
path = this.pathToRegExp(path);
if (!(path instanceof RegExp)) {
throw new Error('Path must be specified as either a string or a RegExp.');
}
var pathString = path.toString();
// normalize protocol to lower-case
protocol = protocol.toLocaleLowerCase();
if (this.findHandlerIndex(pathString, protocol) !== -1) {
throw new Error('You may only mount one handler per path/protocol combination.');
}
this.handlers.push({
'path': path,
'pathString': pathString,
'protocol': protocol,
'callback': callback
});
};
WebSocketRouter.prototype.unmount = function(path, protocol) {
var index = this.findHandlerIndex(this.pathToRegExp(path).toString(), protocol);
if (index !== -1) {
this.handlers.splice(index, 1);
}
else {
throw new Error('Unable to find a route matching the specified path and protocol.');
}
};
WebSocketRouter.prototype.findHandlerIndex = function(pathString, protocol) {
protocol = protocol.toLocaleLowerCase();
for (var i=0, len=this.handlers.length; i < len; i++) {
var handler = this.handlers[i];
if (handler.pathString === pathString && handler.protocol === protocol) {
return i;
}
}
return -1;
};
WebSocketRouter.prototype.pathToRegExp = function(path) {
if (typeof(path) === 'string') {
if (path === '*') {
path = /^.*$/;
}
else {
path = path.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
path = new RegExp('^' + path + '$');
}
}
return path;
};
WebSocketRouter.prototype.handleRequest = function(request) {
var requestedProtocols = request.requestedProtocols;
if (requestedProtocols.length === 0) {
requestedProtocols = ['____no_protocol____'];
}
// Find a handler with the first requested protocol first
for (var i=0; i < requestedProtocols.length; i++) {
var requestedProtocol = requestedProtocols[i].toLocaleLowerCase();
// find the first handler that can process this request
for (var j=0, len=this.handlers.length; j < len; j++) {
var handler = this.handlers[j];
if (handler.path.test(request.resourceURL.pathname)) {
if (requestedProtocol === handler.protocol ||
handler.protocol === '*')
{
var routerRequest = new WebSocketRouterRequest(request, requestedProtocol);
handler.callback(routerRequest);
return;
}
}
}
}
// If we get here we were unable to find a suitable handler.
request.reject(404, 'No handler is available for the given request.');
};
module.exports = WebSocketRouter;

54
node_modules/websocket/lib/WebSocketRouterRequest.js generated vendored Normal file
View File

@ -0,0 +1,54 @@
/************************************************************************
* Copyright 2010-2015 Brian McKelvey.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
var util = require('util');
var EventEmitter = require('events').EventEmitter;
function WebSocketRouterRequest(webSocketRequest, resolvedProtocol) {
// Superclass Constructor
EventEmitter.call(this);
this.webSocketRequest = webSocketRequest;
if (resolvedProtocol === '____no_protocol____') {
this.protocol = null;
}
else {
this.protocol = resolvedProtocol;
}
this.origin = webSocketRequest.origin;
this.resource = webSocketRequest.resource;
this.resourceURL = webSocketRequest.resourceURL;
this.httpRequest = webSocketRequest.httpRequest;
this.remoteAddress = webSocketRequest.remoteAddress;
this.webSocketVersion = webSocketRequest.webSocketVersion;
this.requestedExtensions = webSocketRequest.requestedExtensions;
this.cookies = webSocketRequest.cookies;
}
util.inherits(WebSocketRouterRequest, EventEmitter);
WebSocketRouterRequest.prototype.accept = function(origin, cookies) {
var connection = this.webSocketRequest.accept(this.protocol, origin, cookies);
this.emit('requestAccepted', connection);
return connection;
};
WebSocketRouterRequest.prototype.reject = function(status, reason, extraHeaders) {
this.webSocketRequest.reject(status, reason, extraHeaders);
this.emit('requestRejected', this);
};
module.exports = WebSocketRouterRequest;

245
node_modules/websocket/lib/WebSocketServer.js generated vendored Normal file
View File

@ -0,0 +1,245 @@
/************************************************************************
* Copyright 2010-2015 Brian McKelvey.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
var extend = require('./utils').extend;
var utils = require('./utils');
var util = require('util');
var debug = require('debug')('websocket:server');
var EventEmitter = require('events').EventEmitter;
var WebSocketRequest = require('./WebSocketRequest');
var WebSocketServer = function WebSocketServer(config) {
// Superclass Constructor
EventEmitter.call(this);
this._handlers = {
upgrade: this.handleUpgrade.bind(this),
requestAccepted: this.handleRequestAccepted.bind(this),
requestResolved: this.handleRequestResolved.bind(this)
};
this.connections = [];
this.pendingRequests = [];
if (config) {
this.mount(config);
}
};
util.inherits(WebSocketServer, EventEmitter);
WebSocketServer.prototype.mount = function(config) {
this.config = {
// The http server instance to attach to. Required.
httpServer: null,
// 64KiB max frame size.
maxReceivedFrameSize: 0x10000,
// 1MiB max message size, only applicable if
// assembleFragments is true
maxReceivedMessageSize: 0x100000,
// Outgoing messages larger than fragmentationThreshold will be
// split into multiple fragments.
fragmentOutgoingMessages: true,
// Outgoing frames are fragmented if they exceed this threshold.
// Default is 16KiB
fragmentationThreshold: 0x4000,
// If true, the server will automatically send a ping to all
// clients every 'keepaliveInterval' milliseconds. The timer is
// reset on any received data from the client.
keepalive: true,
// The interval to send keepalive pings to connected clients if the
// connection is idle. Any received data will reset the counter.
keepaliveInterval: 20000,
// If true, the server will consider any connection that has not
// received any data within the amount of time specified by
// 'keepaliveGracePeriod' after a keepalive ping has been sent to
// be dead, and will drop the connection.
// Ignored if keepalive is false.
dropConnectionOnKeepaliveTimeout: true,
// The amount of time to wait after sending a keepalive ping before
// closing the connection if the connected peer does not respond.
// Ignored if keepalive is false.
keepaliveGracePeriod: 10000,
// Whether to use native TCP keep-alive instead of WebSockets ping
// and pong packets. Native TCP keep-alive sends smaller packets
// on the wire and so uses bandwidth more efficiently. This may
// be more important when talking to mobile devices.
// If this value is set to true, then these values will be ignored:
// keepaliveGracePeriod
// dropConnectionOnKeepaliveTimeout
useNativeKeepalive: false,
// If true, fragmented messages will be automatically assembled
// and the full message will be emitted via a 'message' event.
// If false, each frame will be emitted via a 'frame' event and
// the application will be responsible for aggregating multiple
// fragmented frames. Single-frame messages will emit a 'message'
// event in addition to the 'frame' event.
// Most users will want to leave this set to 'true'
assembleFragments: true,
// If this is true, websocket connections will be accepted
// regardless of the path and protocol specified by the client.
// The protocol accepted will be the first that was requested
// by the client. Clients from any origin will be accepted.
// This should only be used in the simplest of cases. You should
// probably leave this set to 'false' and inspect the request
// object to make sure it's acceptable before accepting it.
autoAcceptConnections: false,
// Whether or not the X-Forwarded-For header should be respected.
// It's important to set this to 'true' when accepting connections
// from untrusted clients, as a malicious client could spoof its
// IP address by simply setting this header. It's meant to be added
// by a trusted proxy or other intermediary within your own
// infrastructure.
// See: http://en.wikipedia.org/wiki/X-Forwarded-For
ignoreXForwardedFor: false,
// The Nagle Algorithm makes more efficient use of network resources
// by introducing a small delay before sending small packets so that
// multiple messages can be batched together before going onto the
// wire. This however comes at the cost of latency, so the default
// is to disable it. If you don't need low latency and are streaming
// lots of small messages, you can change this to 'false'
disableNagleAlgorithm: true,
// The number of milliseconds to wait after sending a close frame
// for an acknowledgement to come back before giving up and just
// closing the socket.
closeTimeout: 5000
};
extend(this.config, config);
if (this.config.httpServer) {
if (!Array.isArray(this.config.httpServer)) {
this.config.httpServer = [this.config.httpServer];
}
var upgradeHandler = this._handlers.upgrade;
this.config.httpServer.forEach(function(httpServer) {
httpServer.on('upgrade', upgradeHandler);
});
}
else {
throw new Error('You must specify an httpServer on which to mount the WebSocket server.');
}
};
WebSocketServer.prototype.unmount = function() {
var upgradeHandler = this._handlers.upgrade;
this.config.httpServer.forEach(function(httpServer) {
httpServer.removeListener('upgrade', upgradeHandler);
});
};
WebSocketServer.prototype.closeAllConnections = function() {
this.connections.forEach(function(connection) {
connection.close();
});
this.pendingRequests.forEach(function(request) {
process.nextTick(function() {
request.reject(503); // HTTP 503 Service Unavailable
});
});
};
WebSocketServer.prototype.broadcast = function(data) {
if (Buffer.isBuffer(data)) {
this.broadcastBytes(data);
}
else if (typeof(data.toString) === 'function') {
this.broadcastUTF(data);
}
};
WebSocketServer.prototype.broadcastUTF = function(utfData) {
this.connections.forEach(function(connection) {
connection.sendUTF(utfData);
});
};
WebSocketServer.prototype.broadcastBytes = function(binaryData) {
this.connections.forEach(function(connection) {
connection.sendBytes(binaryData);
});
};
WebSocketServer.prototype.shutDown = function() {
this.unmount();
this.closeAllConnections();
};
WebSocketServer.prototype.handleUpgrade = function(request, socket) {
var wsRequest = new WebSocketRequest(socket, request, this.config);
try {
wsRequest.readHandshake();
}
catch(e) {
wsRequest.reject(
e.httpCode ? e.httpCode : 400,
e.message,
e.headers
);
debug('Invalid handshake: %s', e.message);
return;
}
this.pendingRequests.push(wsRequest);
wsRequest.once('requestAccepted', this._handlers.requestAccepted);
wsRequest.once('requestResolved', this._handlers.requestResolved);
if (!this.config.autoAcceptConnections && utils.eventEmitterListenerCount(this, 'request') > 0) {
this.emit('request', wsRequest);
}
else if (this.config.autoAcceptConnections) {
wsRequest.accept(wsRequest.requestedProtocols[0], wsRequest.origin);
}
else {
wsRequest.reject(404, 'No handler is configured to accept the connection.');
}
};
WebSocketServer.prototype.handleRequestAccepted = function(connection) {
var self = this;
connection.once('close', function(closeReason, description) {
self.handleConnectionClose(connection, closeReason, description);
});
this.connections.push(connection);
this.emit('connect', connection);
};
WebSocketServer.prototype.handleConnectionClose = function(connection, closeReason, description) {
var index = this.connections.indexOf(connection);
if (index !== -1) {
this.connections.splice(index, 1);
}
this.emit('close', connection, closeReason, description);
};
WebSocketServer.prototype.handleRequestResolved = function(request) {
var index = this.pendingRequests.indexOf(request);
if (index !== -1) { this.pendingRequests.splice(index, 1); }
};
module.exports = WebSocketServer;

42
node_modules/websocket/lib/browser.js generated vendored Normal file
View File

@ -0,0 +1,42 @@
var _global = (function() { return this; })();
var NativeWebSocket = _global.WebSocket || _global.MozWebSocket;
var websocket_version = require('./version');
/**
* Expose a W3C WebSocket class with just one or two arguments.
*/
function W3CWebSocket(uri, protocols) {
var native_instance;
if (protocols) {
native_instance = new NativeWebSocket(uri, protocols);
}
else {
native_instance = new NativeWebSocket(uri);
}
/**
* 'native_instance' is an instance of nativeWebSocket (the browser's WebSocket
* class). Since it is an Object it will be returned as it is when creating an
* instance of W3CWebSocket via 'new W3CWebSocket()'.
*
* ECMAScript 5: http://bclary.com/2004/11/07/#a-13.2.2
*/
return native_instance;
}
if (NativeWebSocket) {
['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'].forEach(function(prop) {
Object.defineProperty(W3CWebSocket, prop, {
get: function() { return NativeWebSocket[prop]; }
});
});
}
/**
* Module exports.
*/
module.exports = {
'w3cwebsocket' : NativeWebSocket ? W3CWebSocket : null,
'version' : websocket_version
};

66
node_modules/websocket/lib/utils.js generated vendored Normal file
View File

@ -0,0 +1,66 @@
var noop = exports.noop = function(){};
exports.extend = function extend(dest, source) {
for (var prop in source) {
dest[prop] = source[prop];
}
};
exports.eventEmitterListenerCount =
require('events').EventEmitter.listenerCount ||
function(emitter, type) { return emitter.listeners(type).length; };
exports.bufferAllocUnsafe = Buffer.allocUnsafe ?
Buffer.allocUnsafe :
function oldBufferAllocUnsafe(size) { return new Buffer(size); };
exports.bufferFromString = Buffer.from ?
Buffer.from :
function oldBufferFromString(string, encoding) {
return new Buffer(string, encoding);
};
exports.BufferingLogger = function createBufferingLogger(identifier, uniqueID) {
var logFunction = require('debug')(identifier);
if (logFunction.enabled) {
var logger = new BufferingLogger(identifier, uniqueID, logFunction);
var debug = logger.log.bind(logger);
debug.printOutput = logger.printOutput.bind(logger);
debug.enabled = logFunction.enabled;
return debug;
}
logFunction.printOutput = noop;
return logFunction;
};
function BufferingLogger(identifier, uniqueID, logFunction) {
this.logFunction = logFunction;
this.identifier = identifier;
this.uniqueID = uniqueID;
this.buffer = [];
}
BufferingLogger.prototype.log = function() {
this.buffer.push([ new Date(), Array.prototype.slice.call(arguments) ]);
return this;
};
BufferingLogger.prototype.clear = function() {
this.buffer = [];
return this;
};
BufferingLogger.prototype.printOutput = function(logFunction) {
if (!logFunction) { logFunction = this.logFunction; }
var uniqueID = this.uniqueID;
this.buffer.forEach(function(entry) {
var date = entry[0].toLocaleString();
var args = entry[1].slice();
var formatString = args[0];
if (formatString !== (void 0) && formatString !== null) {
formatString = '%s - %s - ' + formatString.toString();
args.splice(0, 1, formatString, date, uniqueID);
logFunction.apply(global, args);
}
});
};

1
node_modules/websocket/lib/version.js generated vendored Normal file
View File

@ -0,0 +1 @@
module.exports = require('../package.json').version;

11
node_modules/websocket/lib/websocket.js generated vendored Normal file
View File

@ -0,0 +1,11 @@
module.exports = {
'server' : require('./WebSocketServer'),
'client' : require('./WebSocketClient'),
'router' : require('./WebSocketRouter'),
'frame' : require('./WebSocketFrame'),
'request' : require('./WebSocketRequest'),
'connection' : require('./WebSocketConnection'),
'w3cwebsocket' : require('./W3CWebSocket'),
'deprecation' : require('./Deprecation'),
'version' : require('./version')
};