added unit testing, and started implementing unit tests...phew
This commit is contained in:
52
node_modules/websocket/lib/BufferUtil.fallback.js
generated
vendored
Normal file
52
node_modules/websocket/lib/BufferUtil.fallback.js
generated
vendored
Normal 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
17
node_modules/websocket/lib/BufferUtil.js
generated
vendored
Normal 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
32
node_modules/websocket/lib/Deprecation.js
generated
vendored
Normal 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
12
node_modules/websocket/lib/Validation.fallback.js
generated
vendored
Normal 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
17
node_modules/websocket/lib/Validation.js
generated
vendored
Normal 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
257
node_modules/websocket/lib/W3CWebSocket.js
generated
vendored
Normal 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
361
node_modules/websocket/lib/WebSocketClient.js
generated
vendored
Normal 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
896
node_modules/websocket/lib/WebSocketConnection.js
generated
vendored
Normal 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
280
node_modules/websocket/lib/WebSocketFrame.js
generated
vendored
Normal 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
524
node_modules/websocket/lib/WebSocketRequest.js
generated
vendored
Normal 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
157
node_modules/websocket/lib/WebSocketRouter.js
generated
vendored
Normal 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
54
node_modules/websocket/lib/WebSocketRouterRequest.js
generated
vendored
Normal 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
245
node_modules/websocket/lib/WebSocketServer.js
generated
vendored
Normal 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
42
node_modules/websocket/lib/browser.js
generated
vendored
Normal 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
66
node_modules/websocket/lib/utils.js
generated
vendored
Normal 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
1
node_modules/websocket/lib/version.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('../package.json').version;
|
11
node_modules/websocket/lib/websocket.js
generated
vendored
Normal file
11
node_modules/websocket/lib/websocket.js
generated
vendored
Normal 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')
|
||||
};
|
Reference in New Issue
Block a user