New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

websocket

Package Overview
Dependencies
Maintainers
0
Versions
56
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

websocket - npm Package Compare versions

Comparing version 0.0.4 to 0.0.5

3

index.js

@@ -5,3 +5,4 @@ module.exports = {

"router": require('./lib/WebSocketRouter'),
"frame": require('./lib/WebSocketFrame')
"frame": require('./lib/WebSocketFrame'),
"request": require('./lib/WebSocketRequest')
};

@@ -67,162 +67,160 @@ var extend = require('./utils').extend;

extend(WebSocketClient.prototype, {
connect: function(requestUrl, protocols, origin) {
var s = this;
if (!(protocols instanceof Array)) {
protocols = [];
WebSocketClient.prototype.connect = function(requestUrl, protocols, origin) {
var s = this;
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, index, array) {
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) + "'");
}
}
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, index, array) {
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'
};
var defaultPorts = {
'ws:': '80',
'wss:': '443'
};
if (!this.url.port) {
this.url.port = defaultPorts[this.url.protocol];
}
var nonce = new Buffer(16);
for (var i=0; i < 16; i++) {
nonce[i] = Math.round(Math.random()*0xFF);
}
this.base64nonce = nonce.toString('base64');
var requestOptions = {
host: this.url.hostname,
port: this.url.port,
method: 'GET',
path: this.url.path
};
// FIXME: Using old http.createClient interface since Node's new
// Agent-based API is buggy.
var client = this.httpClient = http.createClient(this.url.port, this.url.hostname);
var reqHeaders = {
'Upgrade': 'websocket',
'Connection': 'Upgrade',
'Sec-WebSocket-Version': '8',
'Sec-WebSocket-Key': this.base64nonce,
'Host': this.url.hostname
};
if (this.protocols.length > 0) {
reqHeaders['Sec-WebSocket-Protocol'] = this.protocols.join(', ');
}
if (this.origin) {
reqHeaders['Sec-WebSocket-Origin'] = this.origin;
}
// TODO: Implement extensions
if (!this.url.port) {
this.url.port = defaultPorts[this.url.protocol];
}
var nonce = new Buffer(16);
for (var i=0; i < 16; i++) {
nonce[i] = Math.round(Math.random()*0xFF);
}
this.base64nonce = nonce.toString('base64');
var requestOptions = {
host: this.url.hostname,
port: this.url.port,
method: 'GET',
path: this.url.path
};
// FIXME: Using old http.createClient interface since Node's new
// Agent-based API is buggy.
var client = this.httpClient = http.createClient(this.url.port, this.url.hostname);
var reqHeaders = {
'Upgrade': 'websocket',
'Connection': 'Upgrade',
'Sec-WebSocket-Version': '8',
'Sec-WebSocket-Key': this.base64nonce,
'Host': this.url.hostname
};
if (this.protocols.length > 0) {
reqHeaders['Sec-WebSocket-Protocol'] = this.protocols.join(', ');
}
if (this.origin) {
reqHeaders['Sec-WebSocket-Origin'] = this.origin;
}
// TODO: Implement extensions
var handleRequestError = function(error) {
s.emit('error', error);
}
client.on('error', handleRequestError);
var handleRequestError = function(error) {
s.emit('error', error);
}
client.on('error', handleRequestError);
client.on('upgrade', function handleClientUpgrade(response, socket, head) {
req.removeListener('error', handleRequestError);
s.socket = socket;
s.response = response;
s.firstDataChunk = head;
s.validateHandshake();
});
client.on('upgrade', function handleClientUpgrade(response, socket, head) {
req.removeListener('error', handleRequestError);
s.socket = socket;
s.response = response;
s.firstDataChunk = head;
s.validateHandshake();
});
var req = this.request = client.request(this.url.path, reqHeaders);
var req = this.request = client.request(this.url.path, reqHeaders);
req.on('response', function(response) {
s.failHandshake("Server responded with a non-101 status: " + response.statusCode);
});
req.end();
},
req.on('response', function(response) {
s.failHandshake("Server responded with a non-101 status: " + response.statusCode);
});
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.");
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;
}
}
if (!(headers['connection'] && headers['connection'].toLocaleLowerCase() === 'upgrade')) {
this.failHandshake("Expected a Connection: Upgrade header from the server");
else {
this.failHandshake("Expected a Sec-WebSocket-Protocol header.");
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();
},
}
failHandshake: function(errorDescription) {
if (this.socket && this.socket.writable) {
this.socket.end();
}
this.emit('connectFailed', errorDescription);
},
if (!(headers['connection'] && headers['connection'].toLocaleLowerCase() === 'upgrade')) {
this.failHandshake("Expected a Connection: Upgrade header from the server");
return;
}
succeedHandshake: function() {
var connection = new WebSocketConnection(this.socket, [], this.protocol, true, this.config);
this.emit('connect', connection);
if (this.firstDataChunk.length > 0) {
connection.handleSocketData(this.firstDataChunk);
this.firstDataChunk = null;
}
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', errorDescription);
};
WebSocketClient.prototype.succeedHandshake = function() {
var connection = new WebSocketConnection(this.socket, [], this.protocol, true, this.config);
this.emit('connect', connection);
if (this.firstDataChunk.length > 0) {
connection.handleSocketData(this.firstDataChunk);
this.firstDataChunk = null;
}
};
module.exports = WebSocketClient;

@@ -1,2 +0,1 @@

var extend = require('./utils').extend;
var crypto = require('crypto');

@@ -80,128 +79,150 @@ var util = require('util');

extend(WebSocketConnection.prototype, {
handleSocketData: function(data) {
this.bufferList.write(data);
WebSocketConnection.prototype.handleSocketData = function(data) {
this.bufferList.write(data);
// currentFrame.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.
while (this.connected && this.currentFrame.addData(this.bufferList)) {
// currentFrame.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.
while (this.connected && this.currentFrame.addData(this.bufferList)) {
// Handle possible parsing errors
if (this.currentFrame.protocolError) {
// Something bad happened.. get rid of this client.
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, this.currentFrame.dropReason);
return;
}
else if (this.currentFrame.frameTooLarge) {
this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_LARGE, this.currentFrame.dropReason);
return;
}
if (!this.assembleFragments) {
this.emit('frame', this.currentFrame);
}
this.processFrame(this.currentFrame);
this.currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
// Handle possible parsing errors
if (this.currentFrame.protocolError) {
// Something bad happened.. get rid of this client.
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, this.currentFrame.dropReason);
return;
}
},
handleSocketError: function(error) {
// console.log((new Date()) + " - Closing Connection: Socket Error: " + error);
if (this.listeners('error').length > 0) {
this.emit('error', error);
else if (this.currentFrame.frameTooLarge) {
this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_LARGE, this.currentFrame.dropReason);
return;
}
this.socket.end();
},
if (!this.assembleFragments) {
this.emit('frame', this.currentFrame);
}
this.processFrame(this.currentFrame);
this.currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
}
};
handleSocketEnd: function() {
this.socket.end();
this.frameQueue = null;
this.outgoingFrameQueue = null;
this.fragmentationSize = 0;
this.bufferList = null;
},
WebSocketConnection.prototype.handleSocketError = function(error) {
// console.log((new Date()) + " - Closing Connection: Socket Error: " + error);
if (this.listeners('error').length > 0) {
this.emit('error', error);
}
this.socket.end();
};
handleSocketClose: function(hadError) {
this.socketHadError = hadError;
WebSocketConnection.prototype.handleSocketEnd = function() {
this.socket.end();
this.frameQueue = null;
this.outgoingFrameQueue = null;
this.fragmentationSize = 0;
this.bufferList = null;
};
WebSocketConnection.prototype.handleSocketClose = function(hadError) {
this.socketHadError = hadError;
this.connected = false;
this.state = "closed";
if (!this.closeEventEmitted) {
this.closeEventEmitted = true;
this.emit('close', this);
}
if (this.config.keepalive) {
clearInterval(this._pingIntervalID);
}
};
WebSocketConnection.prototype.handleSocketDrain = function() {
this.outputPaused = false;
this.processOutgoingFrameQueue();
};
WebSocketConnection.prototype.close = function() {
if (this.connected) {
this.setCloseTimer();
this.sendCloseFrame();
this.state = "closing";
this.connected = false;
this.state = "closed";
if (!this.closeEventEmitted) {
this.closeEventEmitted = true;
this.emit('close', this);
}
if (this.config.keepalive) {
clearInterval(this._pingIntervalID);
}
},
handleSocketDrain: function() {
this.outputPaused = false;
this.processOutgoingFrameQueue();
},
close: function() {
if (this.connected) {
this.setCloseTimer();
this.sendCloseFrame();
this.state = "closing";
this.connected = false;
}
},
drop: function(closeReason, reasonText) {
if (typeof(closeReason) !== 'number') {
closeReason = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
}
var logText = "WebSocket: Dropping Connection. Code: " + closeReason.toString(10);
if (reasonText) {
logText += (" - " + reasonText);
}
console.error((new Date()) + " " + logText);
this.outgoingFrameQueue = [];
this.frameQueue = []
this.fragmentationSize = 0;
this.sendCloseFrame(closeReason, reasonText, true);
this.connected = false;
this.state = "closed";
this.socket.destroy();
},
setCloseTimer: function() {
this.clearCloseTimer();
this.waitingForCloseResponse = true;
this.closeTimer = setTimeout(this._closeTimerHandler, this.closeTimeout);
},
clearCloseTimer: function() {
if (this.closeTimer) {
clearTimeout(this.closeTimer);
this.waitingForCloseResponse = false;
this.closeTimer = null;
}
},
handleCloseTimer: function() {
}
};
WebSocketConnection.prototype.drop = function(closeReason, reasonText) {
if (typeof(closeReason) !== 'number') {
closeReason = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
}
var logText = "WebSocket: Dropping Connection. Code: " + closeReason.toString(10);
if (reasonText) {
logText += (" - " + reasonText);
}
console.error((new Date()) + " " + logText);
this.outgoingFrameQueue = [];
this.frameQueue = []
this.fragmentationSize = 0;
this.sendCloseFrame(closeReason, reasonText, true);
this.connected = false;
this.state = "closed";
this.socket.destroy();
};
WebSocketConnection.prototype.setCloseTimer = function() {
this.clearCloseTimer();
this.waitingForCloseResponse = true;
this.closeTimer = setTimeout(this._closeTimerHandler, this.closeTimeout);
};
WebSocketConnection.prototype.clearCloseTimer = function() {
if (this.closeTimer) {
clearTimeout(this.closeTimer);
this.waitingForCloseResponse = false;
this.closeTimer = null;
if (this.waitingForCloseResponse) {
this.waitingForCloseResponse = false;
this.socket.end();
}
},
}
}
WebSocketConnection.prototype.handleCloseTimer = function() {
this.closeTimer = null;
if (this.waitingForCloseResponse) {
this.waitingForCloseResponse = false;
this.socket.end();
}
};
WebSocketConnection.prototype.processFrame = function(frame) {
var i;
var message;
processFrame: function(frame) {
var i;
var message;
switch(frame.opcode) {
case 0x02: // WebSocketFrame.BINARY_FRAME
if (this.assembleFragments) {
if (frame.fin) {
// Complete single-frame message received
this.emit('message', {
type: 'binary',
binaryData: frame.binaryPayload
});
}
else if (this.frameQueue.length === 0) {
switch(frame.opcode) {
case 0x02: // WebSocketFrame.BINARY_FRAME
if (this.assembleFragments) {
if (frame.fin) {
// Complete single-frame message received
this.emit('message', {
type: 'binary',
binaryData: frame.binaryPayload
});
}
else if (this.frameQueue.length === 0) {
// beginning of a fragmented message
this.frameQueue.push(frame);
this.fragmentationOpcode = frame.opcode;
this.fragmentationSize = frame.length;
}
else {
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
"Illegal BINARY_FRAME received in the middle of a fragmented message. Expected a continuation or control frame.");
return;
}
}
break;
case 0x01: // WebSocketFrame.TEXT_FRAME
if (this.assembleFragments) {
if (frame.fin) {
// Complete single-frame message received
this.emit('message', {
type: 'utf8',
utf8Data: frame.binaryPayload.toString('utf8')
});
}
else if (this.frameQueue.length === 0) {
if (this.assembleFragments) {
// beginning of a fragmented message

@@ -212,258 +233,235 @@ this.frameQueue.push(frame);

}
else {
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
"Illegal BINARY_FRAME received in the middle of a fragmented message. Expected a continuation or control frame.");
return;
}
}
break;
case 0x01: // WebSocketFrame.TEXT_FRAME
if (this.assembleFragments) {
if (frame.fin) {
// Complete single-frame message received
this.emit('message', {
type: 'utf8',
utf8Data: frame.binaryPayload.toString('utf8')
});
}
else if (this.frameQueue.length === 0) {
if (this.assembleFragments) {
// beginning of a fragmented message
this.frameQueue.push(frame);
this.fragmentationOpcode = frame.opcode;
this.fragmentationSize = frame.length;
}
}
else {
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
"Illegal TEXT_FRAME received in the middle of a fragmented message. Expected a continuation or control frame.");
return;
}
else {
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
"Illegal TEXT_FRAME received in the middle of a fragmented message. Expected a continuation or control frame.");
return;
}
break;
case 0x00: // WebSocketFrame.CONTINUATION
if (this.assembleFragments) {
if (this.fragmentationOpcode === 0x00 && frame.opcode === 0x00) {
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
"Unexpected Continuation Frame");
return;
}
}
break;
case 0x00: // WebSocketFrame.CONTINUATION
if (this.assembleFragments) {
if (this.fragmentationOpcode === 0x00 && frame.opcode === 0x00) {
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
"Unexpected Continuation Frame");
return;
}
this.fragmentationSize += frame.length;
this.fragmentationSize += frame.length;
if (this.fragmentationSize > this.maxReceivedMessageSize) {
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
"Maximum message size exceeded.");
return;
}
if (this.fragmentationSize > this.maxReceivedMessageSize) {
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
"Maximum message size exceeded.");
return;
}
this.frameQueue.push(frame);
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 = new Buffer(this.fragmentationSize);
this.frameQueue.forEach(function (currentFrame) {
currentFrame.binaryPayload.copy(binaryPayload, bytesCopied);
bytesCopied += currentFrame.binaryPayload.length;
});
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 = new Buffer(this.fragmentationSize);
this.frameQueue.forEach(function (currentFrame) {
currentFrame.binaryPayload.copy(binaryPayload, bytesCopied);
bytesCopied += currentFrame.binaryPayload.length;
});
switch (this.frameQueue[0].opcode) {
case 0x02: // WebSocketOpcode.BINARY_FRAME
this.emit('message', {
type: 'binary',
binaryData: binaryPayload
});
break;
case 0x01: // WebSocketOpcode.TEXT_FRAME
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" + this.frameQueue[0].opcode.toString(16));
return;
}
this.frameQueue = [];
this.fragmentationSize = 0;
switch (this.frameQueue[0].opcode) {
case 0x02: // WebSocketOpcode.BINARY_FRAME
this.emit('message', {
type: 'binary',
binaryData: binaryPayload
});
break;
case 0x01: // WebSocketOpcode.TEXT_FRAME
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" + this.frameQueue[0].opcode.toString(16));
return;
}
this.frameQueue = [];
this.fragmentationSize = 0;
}
break;
case 0x09: // WebSocketFrame.PING
this.pong(frame.binaryPayload);
break;
case 0x0A: // WebSocketFrame.PONG
break;
case 0x08: // WebSocketFrame.CONNECTION_CLOSE
if (this.waitingForCloseResponse) {
// Got response to our request to close the connection.
// Close is complete, so we just hang up.
this.clearCloseTimer();
this.waitingForCloseResponse = false;
this.state = "closed";
this.socket.end();
}
else {
// Got request from other party to close connection.
// Send back acknowledgement and then hang up.
this.state = "closing";
if (frame.closeStatus !== WebSocketConnection.CLOSE_REASON_NORMAL) {
var logCloseError;
switch(frame.closeStatus) {
case WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR:
logCloseError = "Remote peer closed connection: Protocol Error";
break;
case WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_LARGE:
logCloseError = "Remote peer closed connection: Received Message Too Large";
break;
case WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT:
logCloseError = "Remote peer closed connection: Unprocessable Input";
break;
case WebSocketConnection.CLOSE_REASON_GOING_AWAY:
logCloseError = "Remote peer closed connection: Going Away";
break;
default:
logCloseError = "Remote peer closed connection: Status code " + frame.closeStatus.toString(10);
break;
}
if (frame.binaryPayload) {
logCloseError += (" - Description Provided: " + frame.binaryPayload.toString('utf8'));
}
console.error((new Date()) + " " + logCloseError);
}
break;
case 0x09: // WebSocketFrame.PING
this.pong(frame.binaryPayload);
break;
case 0x0A: // WebSocketFrame.PONG
break;
case 0x08: // WebSocketFrame.CONNECTION_CLOSE
if (this.waitingForCloseResponse) {
// Got response to our request to close the connection.
// Close is complete, so we just hang up.
this.clearCloseTimer();
this.waitingForCloseResponse = false;
this.state = "closed";
this.socket.end();
}
else {
// Got request from other party to close connection.
// Send back acknowledgement and then hang up.
this.state = "closing";
if (frame.closeStatus !== WebSocketConnection.CLOSE_REASON_NORMAL) {
var logCloseError;
switch(frame.closeStatus) {
case WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR:
logCloseError = "Remote peer closed connection: Protocol Error";
break;
case WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_LARGE:
logCloseError = "Remote peer closed connection: Received Message Too Large";
break;
case WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT:
logCloseError = "Remote peer closed connection: Unprocessable Input";
break;
case WebSocketConnection.CLOSE_REASON_GOING_AWAY:
logCloseError = "Remote peer closed connection: Going Away";
break;
default:
logCloseError = "Remote peer closed connection: Status code " + frame.closeStatus.toString(10);
break;
}
else {
console.log("Remote peer " + this.remoteAddress + " requested disconnect");
if (frame.binaryPayload) {
logCloseError += (" - Description Provided: " + frame.binaryPayload.toString('utf8'));
}
this.sendCloseFrame(WebSocketConnection.CLOSE_REASON_NORMAL);
this.socket.end();
console.error((new Date()) + " " + logCloseError);
}
break;
default:
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
"Unrecognized Opcode: 0x" + frame.opcode.toString(16));
break;
}
},
else {
console.log((new Date()) + "Remote peer " + this.remoteAddress + " requested disconnect");
}
this.sendCloseFrame(WebSocketConnection.CLOSE_REASON_NORMAL);
this.socket.end();
}
break;
default:
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
"Unrecognized Opcode: 0x" + frame.opcode.toString(16));
break;
}
};
WebSocketConnection.prototype.sendUTF = function(data) {
data = new Buffer(data);
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.opcode = 0x01; // WebSocketOpcode.TEXT_FRAME
frame.binaryPayload = new Buffer(data, 'utf8');
this.fragmentAndSend(frame);
};
sendUTF: function(data) {
data = new Buffer(data);
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.opcode = 0x01; // WebSocketOpcode.TEXT_FRAME
frame.binaryPayload = new Buffer(data, 'utf8');
this.fragmentAndSend(frame);
},
WebSocketConnection.prototype.sendBytes = function(data) {
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.opcode = 0x02; // WebSocketOpcode.BINARY_FRAME
frame.binaryPayload = data;
this.fragmentAndSend(frame);
};
WebSocketConnection.prototype.ping = function() {
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.opcode = 0x09; // WebSocketOpcode.PING
frame.fin = true;
this.sendFrame(frame);
};
sendBytes: function(data) {
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.opcode = 0x02; // WebSocketOpcode.BINARY_FRAME
frame.binaryPayload = data;
this.fragmentAndSend(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) {
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.opcode = 0x0A; // WebSocketOpcode.PONG
frame.binaryPayload = binaryPayload;
frame.fin = true;
this.sendFrame(frame);
};
WebSocketConnection.prototype.fragmentAndSend = function(frame) {
if (frame.opcode > 0x07) {
throw new Error("You cannot fragment control frames.");
}
ping: function() {
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.opcode = 0x09; // WebSocketOpcode.PING
frame.fin = true;
this.sendFrame(frame);
},
var threshold = this.config.fragmentationThreshold;
var length = frame.binaryPayload.length;
// Pong frames have to echo back the contents of the data portion of the
// ping frame exactly, byte for byte.
pong: function(binaryPayload) {
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.opcode = 0x0A; // WebSocketOpcode.PONG
frame.binaryPayload = binaryPayload;
if (this.config.fragmentOutgoingMessages && frame.binaryPayload && length > threshold) {
var numFragments = Math.ceil(length / threshold);
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);
}
}
else {
frame.fin = true;
this.sendFrame(frame);
},
}
};
WebSocketConnection.prototype.sendCloseFrame = function(reasonCode, reasonText, force) {
var reasonLength = 0;
if (typeof(reasonCode) !== 'number') {
reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
}
if (typeof(reasonText) === 'string') {
reasonLength = Buffer.byteLength(reasonText, 'utf8');
}
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.fin = true;
frame.opcode = 0x08; // WebSocketOpcode.CONNECTION_CLOSE
frame.closeStatus = reasonCode;
if (reasonText) {
frame.binaryPayload = new Buffer(reasonText, 'utf8');
}
fragmentAndSend: function(frame) {
if (frame.opcode > 0x07) {
throw new Error("You cannot fragment control frames.");
}
var threshold = this.config.fragmentationThreshold;
var length = frame.binaryPayload.length;
if (this.config.fragmentOutgoingMessages && frame.binaryPayload && length > threshold) {
var numFragments = Math.ceil(length / threshold);
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);
}
}
else {
frame.fin = true;
this.sendFrame(frame);
}
},
sendCloseFrame: function(reasonCode, reasonText, force) {
var reasonLength = 0;
if (typeof(reasonCode) !== 'number') {
reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
}
if (typeof(reasonText) === 'string') {
reasonLength = Buffer.byteLength(reasonText, 'utf8');
}
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
frame.fin = true;
frame.opcode = 0x08; // WebSocketOpcode.CONNECTION_CLOSE
frame.closeStatus = reasonCode;
if (reasonText) {
frame.binaryPayload = new Buffer(reasonText, 'utf8');
}
this.sendFrame(frame, force);
},
sendFrame: function(frame, force) {
frame.mask = this.maskOutgoingPackets;
var buffer = frame.toBuffer();
this.outgoingFrameQueue.unshift(buffer);
this.bytesWaitingToFlush += buffer.length;
if (!this.outputPaused || force) {
this.processOutgoingFrameQueue();
}
},
processOutgoingFrameQueue: function() {
if (this.outputPaused) { return; }
this.sendFrame(frame, force);
};
WebSocketConnection.prototype.sendFrame = function(frame, force) {
frame.mask = this.maskOutgoingPackets;
var buffer = frame.toBuffer();
this.outgoingFrameQueue.unshift(buffer);
this.bytesWaitingToFlush += buffer.length;
if (!this.outputPaused || force) {
this.processOutgoingFrameQueue();
}
};
WebSocketConnection.prototype.processOutgoingFrameQueue = function() {
if (this.outputPaused) { return; }
if (this.outgoingFrameQueue.length > 0) {
var buffer = this.outgoingFrameQueue.pop();
try {
if (this.outgoingFrameQueue.length > 0) {
var buffer = this.outgoingFrameQueue.pop();
var flushed = this.socket.write(buffer);
this.bytesWaitingToFlush -= buffer.length;
if (!flushed) {
this.outputPaused = true;
return;
}
process.nextTick(this.outgoingFrameQueueHandler);
}
var flushed = this.socket.write(buffer);
}
catch(e) {
console.error("Error while writing to socket: " + e.toString());
return;
}
this.bytesWaitingToFlush -= buffer.length;
if (!flushed) {
this.outputPaused = true;
return;
}
process.nextTick(this.outgoingFrameQueueHandler);
}
});
};
module.exports = WebSocketConnection;

@@ -1,2 +0,1 @@

var extend = require('./utils').extend;
var ctio = require('../vendor/node-ctype/ctio-faster');

@@ -25,229 +24,230 @@

extend(WebSocketFrame.prototype, {
addData: function(bufferList, fragmentationType) {
var temp;
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];
WebSocketFrame.prototype.addData = function(bufferList, fragmentationType) {
var temp;
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.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;
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;
}
this.opcode = firstByte & 0x0F;
this.length = secondByte & 0x7F;
if (this.length === 126) {
this.parseState = WAITING_FOR_16_BIT_LENGTH;
}
}
if (this.parseState === WAITING_FOR_16_BIT_LENGTH) {
if (bufferList.length >= 2) {
bufferList.joinInto(this.frameHeader, 2, 0, 2);
bufferList.advance(2);
this.length = ctio.ruint16(this.frameHeader, 'big', 2);
else if (this.length === 127) {
this.parseState = WAITING_FOR_64_BIT_LENGTH;
}
else {
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 = ctio.ruint64(this.frameHeader, 'big', 2);
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_16_BIT_LENGTH) {
if (bufferList.length >= 2) {
bufferList.joinInto(this.frameHeader, 2, 0, 2);
bufferList.advance(2);
this.length = ctio.ruint16(this.frameHeader, 'big', 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 = ctio.ruint64(this.frameHeader, 'big', 2);
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.maskPos = 0;
this.parseState = WAITING_FOR_PAYLOAD;
}
}
else {
}
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.maskPos = 0;
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.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 = new Buffer(0);
this.parseState = COMPLETE;
return true;
}
if (bufferList.length >= this.length) {
this.binaryPayload = bufferList.take(this.length);
bufferList.advance(this.length);
if (this.mask) {
this.applyMask(this.binaryPayload, 0, this.length);
}
if (this.length === 0) {
this.binaryPayload = new Buffer(0);
this.parseState = COMPLETE;
return true;
if (this.opcode === 0x08) { // WebSocketOpcode.CONNECTION_CLOSE
this.closeStatus = ctio.ruint16(this.binaryPayload, 'big', 0);
this.binaryPayload = this.binaryPayload.slice(2);
}
if (bufferList.length >= this.length) {
this.binaryPayload = bufferList.take(this.length);
bufferList.advance(this.length);
if (this.mask) {
this.applyMask(this.binaryPayload, 0, this.length);
}
if (this.opcode === 0x08) { // WebSocketOpcode.CONNECTION_CLOSE
this.closeStatus = ctio.ruint16(this.binaryPayload, 'big', 0);
this.binaryPayload = this.binaryPayload.slice(2);
}
this.parseState = COMPLETE;
return true;
}
}
return false;
},
throwAwayPayload: function(bufferList) {
if (bufferList.length >= this.length) {
bufferList.advance(this.length);
this.parseState = COMPLETE;
return true;
}
return false;
},
applyMask: function(buffer, offset, length) {
var end = offset + length;
for (var i=offset; i < end; i++) {
buffer[i] = buffer[i] ^ this.maskBytes[this.maskPos];
this.maskPos = (this.maskPos + 1) & 3;
}
},
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;
}
}
return false;
};
firstByte |= (this.opcode & 0x0F);
WebSocketFrame.prototype.throwAwayPayload = function(bufferList) {
if (bufferList.length >= this.length) {
bufferList.advance(this.length);
this.parseState = COMPLETE;
return true;
}
return false;
};
// 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 = new Buffer(this.length);
ctio.wuint16(this.closeStatus, 'big', data, 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;
}
WebSocketFrame.prototype.applyMask = function(buffer, offset, length) {
var end = offset + length;
for (var i=offset; i < end; i++) {
buffer[i] = buffer[i] ^ this.maskBytes[this.maskPos];
this.maskPos = (this.maskPos + 1) & 3;
}
};
if (this.length <= 125) {
// encode the length directly into the two-byte frame header
secondByte |= (this.length & 0x7F);
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;
}
else if (this.length > 125 && this.length <= 0xFFFF) {
// Use 16-bit length
secondByte |= 126;
headerLength += 2;
data = new Buffer(this.length);
ctio.wuint16(this.closeStatus, 'big', data, 0);
if (this.length > 2) {
this.binaryPayload.copy(data, 2);
}
else if (this.length > 0xFFFF) {
// Use 64-bit length
secondByte |= 127;
headerLength += 8;
}
}
else if (this.binaryPayload) {
data = this.binaryPayload;
this.length = data.length;
}
else {
this.length = 0;
}
output = new Buffer(this.length + headerLength + (this.mask ? 4 : 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;
}
// write the frame header
output[0] = firstByte;
output[1] = secondByte;
output = new Buffer(this.length + headerLength + (this.mask ? 4 : 0));
outputPos = 2;
if (this.length > 125 && this.length <= 0xFFFF) {
// write 16-bit length
ctio.wuint16(this.length, 'big', output, outputPos);
outputPos += 2;
}
else if (this.length > 0xFFFF) {
// write 64-bit length
ctio.wuint64([0x00000000, this.length], 'big', output, outputPos);
outputPos += 8;
}
if (this.length > 0) {
if (this.mask) {
if (!nullMask) {
// Generate a mask key
maskKey = parseInt(Math.random()*0xFFFFFFFF);
}
else {
maskKey = 0x00000000;
}
ctio.wuint32(maskKey, 'big', this.maskBytes, 0);
this.maskPos = 0;
// write the frame header
output[0] = firstByte;
output[1] = secondByte;
// write the mask key
this.maskBytes.copy(output, outputPos);
outputPos += 4;
data.copy(output, outputPos);
this.applyMask(output, outputPos, data.length);
outputPos = 2;
if (this.length > 125 && this.length <= 0xFFFF) {
// write 16-bit length
ctio.wuint16(this.length, 'big', output, outputPos);
outputPos += 2;
}
else if (this.length > 0xFFFF) {
// write 64-bit length
ctio.wuint64([0x00000000, this.length], 'big', output, outputPos);
outputPos += 8;
}
if (this.length > 0) {
if (this.mask) {
if (!nullMask) {
// Generate a mask key
maskKey = parseInt(Math.random()*0xFFFFFFFF);
}
else {
data.copy(output, outputPos);
maskKey = 0x00000000;
}
ctio.wuint32(maskKey, 'big', this.maskBytes, 0);
this.maskPos = 0;
// write the mask key
this.maskBytes.copy(output, outputPos);
outputPos += 4;
data.copy(output, outputPos);
this.applyMask(output, outputPos, data.length);
}
return output;
},
else {
data.copy(output, outputPos);
}
}
toString: function() {
return "Opcode: " + this.opcode + ", fin: " + this.fin + ", length: " + this.length + ", hasPayload: " + Boolean(this.binaryPayload) + ", masked: " + this.mask;
}
});
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;

@@ -1,2 +0,1 @@

var extend = require('./utils').extend;
var crypto = require('crypto');

@@ -68,145 +67,145 @@ var util = require('util');

extend(WebSocketRequest.prototype, {
readHandshake: function(request) {
if (!request.headers['host']) {
throw new Error("Client must provide a Host header.");
}
this.key = request.headers['sec-websocket-key'];
if (!this.key) {
this.reject(400, "Client must provide a value for Sec-WebSocket-Key.");
throw new Error("Client must provide a value for Sec-WebSocket-Key.");
}
this.origin = request.headers['sec-websocket-origin'];
this.websocketVersion = request.headers['sec-websocket-version'];
if (!this.websocketVersion) {
this.reject(400, "Client must provide a value for Sec-WebSocket-Version.");
throw new Error("Client must provide a value for Sec-WebSocket-Version.");
}
if (this.websocketVersion !== '8') {
this.reject(426, "Unsupported websocket client version.", {
"Sec-WebSocket-Version": "8"
});
throw new Error("Unsupported websocket client version. Client requested version " + this.websocketVersion + ", but we only support version 8.");
}
// Protocol is optional.
var protocolString = request.headers['sec-websocket-protocol'];
if (protocolString) {
this.requestedProtocols = protocolString.toLocaleLowerCase().split(headerValueSplitRegExp);
}
else {
this.requestedProtocols = [];
}
if (request.headers['x-forwarded-for']) {
this.remoteAddress = request.headers['x-forwarded-for'].split(', ')[0];
}
// Extensions are optional.
var extensionsString = request.headers['sec-websocket-extensions'];
this.requestedExtensions = this.parseExtensions(extensionsString);
},
parseExtensions: function(extensionsString) {
if (!extensionsString || extensionsString.length === 0) {
return [];
}
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);
});
WebSocketRequest.prototype.readHandshake = function(request) {
if (!request.headers['host']) {
throw new Error("Client must provide a Host header.");
}
this.key = request.headers['sec-websocket-key'];
if (!this.key) {
this.reject(400, "Client must provide a value for Sec-WebSocket-Key.");
throw new Error("Client must provide a value for Sec-WebSocket-Key.");
}
this.origin = request.headers['sec-websocket-origin'];
this.websocketVersion = request.headers['sec-websocket-version'];
if (!this.websocketVersion) {
this.reject(400, "Client must provide a value for Sec-WebSocket-Version.");
throw new Error("Client must provide a value for Sec-WebSocket-Version.");
}
if (this.websocketVersion !== '8') {
this.reject(426, "Unsupported websocket client version.", {
"Sec-WebSocket-Version": "8"
});
throw new Error("Unsupported websocket client version. Client requested version " + this.websocketVersion + ", but we only support version 8.");
}
// Protocol is optional.
var protocolString = request.headers['sec-websocket-protocol'];
if (protocolString) {
this.requestedProtocols = protocolString.toLocaleLowerCase().split(headerValueSplitRegExp);
}
else {
this.requestedProtocols = [];
}
if (request.headers['x-forwarded-for']) {
this.remoteAddress = request.headers['x-forwarded-for'].split(', ')[0];
}
// Extensions are optional.
var extensionsString = request.headers['sec-websocket-extensions'];
this.requestedExtensions = this.parseExtensions(extensionsString);
};
WebSocketRequest.prototype.parseExtensions = function(extensionsString) {
if (!extensionsString || extensionsString.length === 0) {
return [];
}
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: extensionName,
params: extensionParams
name: arr[0],
value: arr[1]
};
array.splice(index, 1, obj);
});
return extensions;
},
accept: function(acceptedProtocol, allowedOrigin) {
// TODO: Handle extensions
var connection = new WebSocketConnection(this.socket, [], acceptedProtocol, false, this.serverConfig);
connection.remoteAddress = this.remoteAddress;
// 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 (acceptedProtocol) {
// validate protocol
for (var i=0; i < acceptedProtocol.length; i++) {
var charCode = acceptedProtocol.charCodeAt(i);
var character = acceptedProtocol.charAt(i);
if (charCode < 0x21 || charCode > 0x7E || protocolSeparators.indexOf(character) !== -1) {
this.reject(500);
throw new Error("Illegal character '" + String.fromCharCode(character) + "' in subprotocol.");
}
}
if (this.requestedProtocols.indexOf(acceptedProtocol) === -1) {
var obj = {
name: extensionName,
params: extensionParams
};
array.splice(index, 1, obj);
});
return extensions;
},
WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin) {
// TODO: Handle extensions
var connection = new WebSocketConnection(this.socket, [], acceptedProtocol, false, this.serverConfig);
connection.remoteAddress = this.remoteAddress;
// 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 (acceptedProtocol) {
// validate protocol
for (var i=0; i < acceptedProtocol.length; i++) {
var charCode = acceptedProtocol.charCodeAt(i);
var character = acceptedProtocol.charAt(i);
if (charCode < 0x21 || charCode > 0x7E || protocolSeparators.indexOf(character) !== -1) {
this.reject(500);
throw new Error("Specified protocol was not requested by the client.");
throw new Error("Illegal character '" + String.fromCharCode(character) + "' in subprotocol.");
}
acceptedProtocol = acceptedProtocol.replace(headerSanitizeRegExp, '');
response += "Sec-WebSocket-Protocol: " + acceptedProtocol + "\r\n";
}
if (this.requestedProtocols.indexOf(acceptedProtocol) === -1) {
this.reject(500);
throw new Error("Specified protocol was not requested by the client.");
}
acceptedProtocol = acceptedProtocol.replace(headerSanitizeRegExp, '');
response += "Sec-WebSocket-Protocol: " + acceptedProtocol + "\r\n";
}
if (allowedOrigin) {
allowedOrigin = allowedOrigin.replace(headerSanitizeRegExp, '');
response += "Sec-WebSocket-Origin: " + allowedOrigin + "\r\n";
}
// TODO: handle negotiated extensions
// if (negotiatedExtensions) {
// response += "Sec-WebSocket-Extensions: " + negotiatedExtensions.join(", ") + "\r\n";
// }
response += "\r\n";
this.socket.write(response, 'ascii');
this.emit('requestAccepted', connection);
return connection;
};
WebSocketRequest.prototype.reject = function(status, reason, extraHeaders) {
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 += (key + ": " + sanitizedValue + "\r\n");
}
if (allowedOrigin) {
allowedOrigin = allowedOrigin.replace(headerSanitizeRegExp, '');
response += "Sec-WebSocket-Origin: " + allowedOrigin + "\r\n";
}
// TODO: handle negotiated extensions
// if (negotiatedExtensions) {
// response += "Sec-WebSocket-Extensions: " + negotiatedExtensions.join(", ") + "\r\n";
// }
response += "\r\n";
this.socket.write(response, 'ascii');
this.emit('requestAccepted', connection);
return connection;
},
reject: function(status, reason, extraHeaders) {
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 += (key + ": " + sanitizedValue + "\r\n");
}
}
response += "\r\n";
this.socket.end(response, 'ascii');
this.emit('requestRejected', this);
}
});
response += "\r\n";
this.socket.end(response, 'ascii');
this.emit('requestRejected', this);
};
module.exports = WebSocketRequest;

@@ -24,112 +24,112 @@ var extend = require('./utils').extend;

extend(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.");
}
},
detachServer: function() {
if (this.server) {
this.server.removeListener('request', this._requestHandler);
this.server = null;
}
else {
throw new Error("Cannot detach from server: not attached.")
}
},
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.");
}
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.");
}
};
var pathString;
if (typeof(path) === 'string') {
if (path === '*') {
path = /^.*$/;
pathString = '*'
}
else {
path = new RegExp('^' + path + '$');
pathString = path.toString();
}
}
if (!(path instanceof RegExp)) {
throw new Error("Path must be specified as either a string or a RegExp.");
}
// normalize protocol to lower-case
protocol = protocol.toLocaleLowerCase();
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.")
}
};
if (this.findHandlerIndex(pathString, protocol) !== -1) {
throw new Error("You may only mount one handler per path/protocol combination.");
}
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.");
}
this.handlers.push({
'path': path,
'pathString': pathString,
'protocol': protocol,
'callback': callback
});
},
unmount: function(path, protocol) {
var index = this.findHandlerIndex(path.toString(), protocol);
if (index !== -1) {
this.handlers.splice(index, 1);
var pathString;
if (typeof(path) === 'string') {
if (path === '*') {
path = /^.*$/;
pathString = '*'
}
else {
throw new Error("Unable to find a route matching the specified path and protocol.");
path = new RegExp('^' + path + '$');
pathString = path.toString();
}
},
}
if (!(path instanceof RegExp)) {
throw new Error("Path must be specified as either a string or a RegExp.");
}
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;
}
// 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(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;
},
}
return -1;
};
WebSocketRouter.prototype.handleRequest = function(request) {
var requestedProtocols = request.requestedProtocols;
if (requestedProtocols.length === 0) {
requestedProtocols = ['____no_protocol____'];
}
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];
// Find a handler with the first requested protocol first
for (var i=0; i < requestedProtocols.length; i++) {
var requestedProtocol = requestedProtocols[i];
// 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.resource)) {
if (requestedProtocol === handler.protocol ||
handler.protocol === '*')
{
var routerRequest = new WebSocketRouterRequest(request, requestedProtocol);
handler.callback(routerRequest);
return;
}
// 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.resource)) {
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.");
}
});
// 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;

@@ -1,3 +0,1 @@

var extend = require('./utils').extend;
function WebSocketRouterRequest(webSocketRequest, resolvedProtocol) {

@@ -16,11 +14,10 @@ this.webSocketRequest = webSocketRequest;

extend(WebSocketRouterRequest.prototype, {
accept: function(origin) {
return this.webSocketRequest.accept(this.protocol, origin);
},
reject: function(status, reason) {
return this.webSocketRequest.reject(status, reason);
}
});
WebSocketRouterRequest.prototype.accept = function(origin) {
return this.webSocketRequest.accept(this.protocol, origin);
};
WebSocketRouterRequest.prototype.reject = function(status, reason) {
return this.webSocketRequest.reject(status, reason);
};
module.exports = WebSocketRouterRequest;

@@ -20,122 +20,119 @@ var extend = require('./utils').extend;

extend(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.
keepalive: true,
// The interval to send keepalive pings to connected clients.
keepaliveInterval: 20000,
// 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,
// 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);
WebSocketServer.prototype.mount = function(config) {
this.config = {
// The http server instance to attach to. Required.
httpServer: null,
// this.httpServer = httpServer;
// if (typeof(pathRegExp) === 'string') {
// pathRegExp = new RegExp('^' + pathRegExp + '$');
// }
// this.pathRegExp = pathRegExp;
// this.protocol = protocol;
if (this.config.httpServer) {
this.config.httpServer.on('upgrade', this._handlers.upgrade);
}
else {
throw new Error("You must specify an httpServer on which to mount the WebSocket server.")
}
},
// 64KiB max frame size.
maxReceivedFrameSize: 0x10000,
unmount: function() {
this.config.httpServer.removeListener('upgrade', this._handlers.upgrade);
},
closeAllConnections: function() {
this.connections.forEach(function(connection) {
connection.close();
});
},
shutDown: function() {
this.unmount();
this.closeAllConnections();
},
handleUpgrade: function(request, socket, head) {
var wsRequest = new WebSocketRequest(socket, request, this.config);
wsRequest.once('requestAccepted', this._handlers.requestAccepted);
// 1MiB max message size, only applicable if
// assembleFragments is true
maxReceivedMessageSize: 0x100000,
try {
wsRequest.readHandshake(request);
}
catch(e) {
console.error((new Date()) + " WebSocket: Invalid handshake: " + e.toString());
return;
}
// Outgoing messages larger than fragmentationThreshold will be
// split into multiple fragments.
fragmentOutgoingMessages: true,
if (!this.config.autoAcceptConnections && this.listeners('request').length > 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.");
}
},
// 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.
keepalive: true,
// The interval to send keepalive pings to connected clients.
keepaliveInterval: 20000,
// 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,
// 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);
handleRequestAccepted: function(connection) {
connection.once('close', this._handlers.connectionClose);
this.connections.push(connection);
this.emit('connect', connection);
},
// this.httpServer = httpServer;
// if (typeof(pathRegExp) === 'string') {
// pathRegExp = new RegExp('^' + pathRegExp + '$');
// }
// this.pathRegExp = pathRegExp;
// this.protocol = protocol;
if (this.config.httpServer) {
this.config.httpServer.on('upgrade', this._handlers.upgrade);
}
else {
throw new Error("You must specify an httpServer on which to mount the WebSocket server.")
}
};
WebSocketServer.prototype.unmount = function() {
this.config.httpServer.removeListener('upgrade', this._handlers.upgrade);
};
WebSocketServer.prototype.closeAllConnections = function() {
this.connections.forEach(function(connection) {
connection.close();
});
};
WebSocketServer.prototype.shutDown = function() {
this.unmount();
this.closeAllConnections();
};
WebSocketServer.prototype.handleUpgrade = function(request, socket, head) {
var wsRequest = new WebSocketRequest(socket, request, this.config);
wsRequest.once('requestAccepted', this._handlers.requestAccepted);
handleConnectionClose: function(connection) {
var index = this.connections.indexOf(connection);
if (index !== -1) {
this.connections.splice(index, 1);
}
this.emit('close', connection);
try {
wsRequest.readHandshake(request);
}
});
catch(e) {
console.error((new Date()) + " WebSocket: Invalid handshake: " + e.toString());
return;
}
if (!this.config.autoAcceptConnections && this.listeners('request').length > 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) {
connection.once('close', this._handlers.connectionClose);
this.connections.push(connection);
this.emit('connect', connection);
};
WebSocketServer.prototype.handleConnectionClose = function(connection) {
var index = this.connections.indexOf(connection);
if (index !== -1) {
this.connections.splice(index, 1);
}
this.emit('close', connection);
};
module.exports = WebSocketServer;

@@ -6,3 +6,3 @@ {

"author": "Brian McKelvey <brian@worlize.com>",
"version": "0.0.4",
"version": "0.0.5",
"repository": {

@@ -9,0 +9,0 @@ "type": "git",

@@ -52,5 +52,9 @@ #!/usr/bin/env node

var messageSize = 0;
var startTime;
var byteCounter;
client.on('connect', function(connection) {
console.log("Connected");
startTime = new Date();
byteCounter = 0;

@@ -68,2 +72,3 @@ connection.on('error', function(error) {

console.log("Received utf-8 message of " + message.utf8Data.length + " characters.");
logThroughput(message.utf8Data.length);
requestData();

@@ -73,2 +78,3 @@ }

console.log("Received binary message of " + message.binaryData.length + " bytes.");
logThroughput(message.binaryData.length);
requestData();

@@ -83,2 +89,3 @@ }

console.log("Total message size: " + messageSize + " bytes.");
logThroughput(messageSize);
messageSize = 0;

@@ -89,2 +96,13 @@ requestData();

function logThroughput(numBytes) {
byteCounter += numBytes;
var duration = (new Date()).valueOf() - startTime.valueOf();
if (duration > 1000) {
var kiloBytesPerSecond = Math.round((byteCounter / 1024) / (duration/1000));
console.log(" Throughput: " + kiloBytesPerSecond + " KBps");
startTime = new Date();
byteCounter = 0;
}
};
function requestData() {

@@ -91,0 +109,0 @@ if (args.binary) {

@@ -135,3 +135,3 @@ #!/usr/bin/env node

console.log("Point your draft-08 compliant browser at http://localhost:" + args.port + "/");
console.log("Point your draft-09 compliant browser at http://localhost:" + args.port + "/");
if (args['no-fragmentation']) {

@@ -138,0 +138,0 @@ console.log("Fragmentation disabled.");

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc