Comparing version
@@ -0,1 +1,7 @@ | ||
v0.4.8 - Feb 29th 2012 | ||
===================== | ||
* Allow verifyClient to run asynchronously [karlsequin] | ||
* Various bugfixes and cleanups. [einaros] | ||
v0.4.7 - Feb 21st 2012 | ||
@@ -2,0 +8,0 @@ ===================== |
@@ -19,9 +19,3 @@ /*! | ||
function Sender (socket, options) { | ||
options = new Options({ | ||
sendBufferCacheSize: 65536 | ||
}).merge(options); | ||
if (options.value.sendBufferCacheSize > 0) { | ||
this._sendCacheSize = options.value.sendBufferCacheSize; | ||
this._sendCache = new Buffer(this._sendCacheSize); | ||
} | ||
options = new Options({}).merge(options); | ||
this._socket = socket; | ||
@@ -100,3 +94,4 @@ this.firstFragment = true; | ||
Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, cb) { | ||
var dontModifyData = true; | ||
var canModifyData = false; | ||
if (!data) { | ||
@@ -112,9 +107,12 @@ try { | ||
} | ||
else if (!Buffer.isBuffer(data)) { | ||
dontModifyData = false; | ||
if (!Buffer.isBuffer(data)) { | ||
canModifyData = true; | ||
data = (data && typeof data.buffer !== 'undefined') ? getArrayBuffer(data.buffer) : new Buffer(data); | ||
} | ||
var dataLength = data.length | ||
, dataOffset = maskData ? 6 : 2 | ||
, secondByte = dataLength; | ||
if (dataLength >= 65536) { | ||
@@ -128,7 +126,8 @@ dataOffset += 8; | ||
} | ||
var totalLength = (maskData && dontModifyData) ? dataLength + dataOffset : dataOffset; | ||
var outputBuffer = (this._sendCache && totalLength <= this._sendCacheSize) | ||
? ((totalLength == this._sendCacheSize ? this._sendCache : this._sendCache.slice(0, totalLength))) | ||
: new Buffer(totalLength); | ||
var totalLength = (maskData && !canModifyData) ? dataLength + dataOffset : dataOffset; | ||
var outputBuffer = new Buffer(totalLength); | ||
outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode; | ||
switch (secondByte) { | ||
@@ -142,4 +141,6 @@ case 126: | ||
} | ||
var sendsDone = 0; | ||
var cbCaller = function() { if (++sendsDone == 2 && typeof cb == 'function') cb(null); } | ||
if (maskData) { | ||
@@ -152,3 +153,3 @@ outputBuffer[1] = secondByte | 0x80; | ||
outputBuffer[dataOffset - 1] = mask[3]; | ||
if (dontModifyData) { | ||
if (!canModifyData) { | ||
bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength); | ||
@@ -155,0 +156,0 @@ try { |
@@ -99,3 +99,3 @@ /*! | ||
WebSocket.prototype.close = function(code, data) { | ||
if (this.readyState == WebSocket.CLOSING) return; | ||
if (this.readyState == WebSocket.CLOSING || this.readyState == WebSocket.CLOSED) return; | ||
if (this.readyState == WebSocket.CONNECTING) { | ||
@@ -105,3 +105,2 @@ this._readyState = WebSocket.CLOSED; | ||
} | ||
if (this.readyState != WebSocket.OPEN) throw new Error('not opened'); | ||
try { | ||
@@ -136,7 +135,11 @@ this._readyState = WebSocket.CLOSING; | ||
* @param {Object} Members - mask: boolean, binary: boolean | ||
* @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open | ||
* @api public | ||
*/ | ||
WebSocket.prototype.ping = function(data, options) { | ||
if (this.readyState != WebSocket.OPEN) throw new Error('not opened'); | ||
WebSocket.prototype.ping = function(data, options, dontFailWhenClosed) { | ||
if (this.readyState != WebSocket.OPEN) { | ||
if (dontFailWhenClosed === true) return; | ||
throw new Error('not opened'); | ||
} | ||
options = options || {}; | ||
@@ -152,7 +155,11 @@ if (typeof options.mask == 'undefined') options.mask = !this._isServer; | ||
* @param {Object} Members - mask: boolean, binary: boolean | ||
* @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open | ||
* @api public | ||
*/ | ||
WebSocket.prototype.pong = function(data, options) { | ||
if (this.readyState != WebSocket.OPEN) throw new Error('not opened'); | ||
WebSocket.prototype.pong = function(data, options, dontFailWhenClosed) { | ||
if (this.readyState != WebSocket.OPEN) { | ||
if (dontFailWhenClosed === true) return; | ||
throw new Error('not opened'); | ||
} | ||
options = options || {}; | ||
@@ -543,3 +550,3 @@ if (typeof options.mask == 'undefined') options.mask = !this._isServer; | ||
flags = flags || {}; | ||
self.pong(data, {mask: !self._isServer, binary: flags.binary === true}); | ||
self.pong(data, {mask: !self._isServer, binary: flags.binary === true}, true); | ||
self.emit('ping', data, flags); | ||
@@ -546,0 +553,0 @@ }); |
@@ -175,2 +175,4 @@ /*! | ||
req.headers['origin']; | ||
var args = [req, socket, upgradeHead, version, cb]; | ||
if (typeof this.options.verifyClient == 'function') { | ||
@@ -182,3 +184,11 @@ var info = { | ||
}; | ||
if (!this.options.verifyClient(info)) { | ||
if (this.options.verifyClient.length == 2) { | ||
var self = this; | ||
this.options.verifyClient(info, function(result) { | ||
if (!result) abortConnection(socket, 401, 'Unauthorized') | ||
else completeUpgrade.apply(self, args); | ||
}); | ||
return; | ||
} | ||
else if (!this.options.verifyClient(info)) { | ||
abortConnection(socket, 401, 'Unauthorized'); | ||
@@ -189,4 +199,8 @@ return; | ||
var protocol = req.headers['sec-websocket-protocol']; | ||
completeUpgrade.apply(this, args); | ||
} | ||
function completeUpgrade(req, socket, upgradeHead, version, cb) { | ||
var protocol = req.headers['sec-websocket-protocol']; | ||
// calc key | ||
@@ -243,2 +257,92 @@ var key = req.headers['sec-websocket-key']; | ||
// setup handshake completion to run after client has been verified | ||
var self = this; | ||
var onClientVerified = function() { | ||
var protocol = req.headers['sec-websocket-protocol']; | ||
// handshake completion code to run once nonce has been successfully retrieved | ||
var completeHandshake = function(nonce, rest) { | ||
// calculate key | ||
var k1 = req.headers['sec-websocket-key1'] | ||
, k2 = req.headers['sec-websocket-key2'] | ||
, md5 = crypto.createHash('md5'); | ||
[k1, k2].forEach(function (k) { | ||
var n = parseInt(k.replace(/[^\d]/g, '')) | ||
, spaces = k.replace(/[^ ]/g, '').length; | ||
if (spaces === 0 || n % spaces !== 0){ | ||
abortConnection(socket, 400, 'Bad Request'); | ||
return; | ||
} | ||
n /= spaces; | ||
md5.update(String.fromCharCode( | ||
n >> 24 & 0xFF, | ||
n >> 16 & 0xFF, | ||
n >> 8 & 0xFF, | ||
n & 0xFF)); | ||
}); | ||
md5.update(nonce.toString('binary')); | ||
var headers = [ | ||
'HTTP/1.1 101 Switching Protocols' | ||
, 'Upgrade: WebSocket' | ||
, 'Connection: Upgrade' | ||
, 'Sec-WebSocket-Location: ' + location | ||
]; | ||
if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); | ||
if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); | ||
socket.setTimeout(0); | ||
socket.setNoDelay(true); | ||
try { | ||
socket.write(headers.concat('', '').join('\r\n')); | ||
socket.write(md5.digest('binary'), 'binary'); | ||
} | ||
catch (e) { | ||
try { socket.end(); } catch (_) {} | ||
return; | ||
} | ||
var client = new WebSocket([req, socket, rest], { | ||
protocolVersion: 'hixie-76', | ||
protocol: protocol | ||
}); | ||
self._clients.push(client); | ||
client.on('close', function() { | ||
var index = self._clients.indexOf(client); | ||
if (index != -1) { | ||
self._clients.splice(index, 1); | ||
} | ||
}); | ||
cb(client); | ||
} | ||
// retrieve nonce | ||
var nonceLength = 8; | ||
if (upgradeHead && upgradeHead.length >= nonceLength) { | ||
var nonce = upgradeHead.slice(0, nonceLength); | ||
var rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; | ||
completeHandshake.call(self, nonce, rest); | ||
} | ||
else { | ||
// nonce not present in upgradeHead, so we must wait for enough data | ||
// data to arrive before continuing | ||
var nonce = new Buffer(nonceLength); | ||
upgradeHead.copy(nonce, 0); | ||
var received = upgradeHead.length; | ||
var rest = null; | ||
var handler = function (data) { | ||
var toRead = Math.min(data.length, nonceLength - received); | ||
if (toRead === 0) return; | ||
data.copy(nonce, received, 0, toRead); | ||
received += toRead; | ||
if (received == nonceLength) { | ||
socket.removeListener('data', handler); | ||
if (toRead < data.length) rest = data.slice(toRead); | ||
completeHandshake.call(self, nonce, rest); | ||
} | ||
} | ||
socket.on('data', handler); | ||
} | ||
} | ||
// verify client | ||
@@ -253,98 +357,16 @@ var location = (socket.encrypted ? 'wss' : 'ws') + '://' + req.headers.host + req.url | ||
}; | ||
if (!this.options.verifyClient(info)) { | ||
abortConnection(socket, 401, 'Unauthorized'); | ||
if (this.options.verifyClient.length == 2) { | ||
var self = this; | ||
this.options.verifyClient(info, function(result) { | ||
if (!result) abortConnection(socket, 401, 'Unauthorized') | ||
else onClientVerified.apply(self); | ||
}); | ||
return; | ||
} | ||
} | ||
var protocol = req.headers['sec-websocket-protocol']; | ||
var completeHandshake = function(nonce, rest) { | ||
// calculate key | ||
var k1 = req.headers['sec-websocket-key1'] | ||
, k2 = req.headers['sec-websocket-key2'] | ||
, md5 = crypto.createHash('md5') | ||
, self = this; | ||
[k1, k2].forEach(function (k) { | ||
var n = parseInt(k.replace(/[^\d]/g, '')) | ||
, spaces = k.replace(/[^ ]/g, '').length; | ||
if (spaces === 0 || n % spaces !== 0){ | ||
abortConnection(socket, 400, 'Bad Request'); | ||
return; | ||
} | ||
n /= spaces; | ||
md5.update(String.fromCharCode( | ||
n >> 24 & 0xFF, | ||
n >> 16 & 0xFF, | ||
n >> 8 & 0xFF, | ||
n & 0xFF)); | ||
}); | ||
md5.update(nonce.toString('binary')); | ||
var headers = [ | ||
'HTTP/1.1 101 Switching Protocols' | ||
, 'Upgrade: WebSocket' | ||
, 'Connection: Upgrade' | ||
, 'Sec-WebSocket-Location: ' + location | ||
]; | ||
if (typeof protocol != 'undefined') { | ||
headers.push('Sec-WebSocket-Protocol: ' + protocol); | ||
} | ||
if (typeof origin != 'undefined') { | ||
headers.push('Sec-WebSocket-Origin: ' + origin); | ||
} | ||
socket.setTimeout(0); | ||
socket.setNoDelay(true); | ||
try { | ||
socket.write(headers.concat('', '').join('\r\n')); | ||
socket.write(md5.digest('binary'), 'binary'); | ||
} | ||
catch (e) { | ||
try { socket.end(); } catch (_) {} | ||
else if (!this.options.verifyClient(info)) { | ||
abortConnection(socket, 401, 'Unauthorized'); | ||
return; | ||
} | ||
var client = new WebSocket([req, socket, rest], { | ||
protocolVersion: 'hixie-76', | ||
protocol: protocol | ||
}); | ||
this._clients.push(client); | ||
var self = this; | ||
client.on('close', function() { | ||
var index = self._clients.indexOf(client); | ||
if (index != -1) { | ||
self._clients.splice(index, 1); | ||
} | ||
}); | ||
cb(client); | ||
} | ||
var nonceLength = 8; | ||
if (upgradeHead && upgradeHead.length >= nonceLength) { | ||
var nonce = upgradeHead.slice(0, nonceLength); | ||
var rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; | ||
completeHandshake.call(this, nonce, rest); | ||
} | ||
else { | ||
// nonce not present in upgradeHead, so we must wait for enough data | ||
// data to arrive before continuing | ||
var nonce = new Buffer(nonceLength); | ||
upgradeHead.copy(nonce, 0); | ||
var received = upgradeHead.length; | ||
var rest = null; | ||
var self = this; | ||
var handler = function (data) { | ||
var toRead = Math.min(data.length, nonceLength - received); | ||
if (toRead === 0) return; | ||
data.copy(nonce, received, 0, toRead); | ||
received += toRead; | ||
if (received == nonceLength) { | ||
socket.removeListener('data', handler); | ||
if (toRead < data.length) rest = data.slice(toRead); | ||
completeHandshake.call(self, nonce, rest); | ||
} | ||
} | ||
socket.on('data', handler); | ||
} | ||
onClientVerified(); | ||
} | ||
@@ -351,0 +373,0 @@ |
@@ -5,3 +5,3 @@ { | ||
"description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455", | ||
"version": "0.4.7", | ||
"version": "0.4.8", | ||
"repository": { | ||
@@ -29,4 +29,5 @@ "type": "git", | ||
"benchmark": "0.3.x", | ||
"tinycolor": "0.x" | ||
"tinycolor": "0.x", | ||
"ansi": "latest" | ||
} | ||
} |
var Sender = require('../lib/Sender'); | ||
require('should'); | ||
describe('Sender', function() { | ||
it('creates a send cache equal to options.sendBufferCacheSize', function() { | ||
var sender = new Sender(null, { | ||
sendBufferCacheSize: 10 | ||
}); | ||
sender._sendCache.length.should.eql(10); | ||
}); | ||
it('keeps a send cache equal to null if options.sendBufferCacheSize is 0', function() { | ||
var sender = new Sender(null, { | ||
sendBufferCacheSize: 0 | ||
}); | ||
(typeof sender._sendCache).should.eql('undefined'); | ||
}); | ||
it('keeps a send cache equal to null if options.sendBufferCacheSize is -1', function() { | ||
var sender = new Sender(null, { | ||
sendBufferCacheSize: -1 | ||
}); | ||
(typeof sender._sendCache).should.eql('undefined'); | ||
}); | ||
describe('Sender', function() { | ||
describe('#frameAndSend', function() { | ||
it('does not modify a masked binary buffer', function() { | ||
var sender = new Sender({ write: function() {} }); | ||
var sender = new Sender({ write: function() {} }); | ||
var buf = new Buffer([1, 2, 3, 4, 5]); | ||
@@ -39,3 +18,3 @@ sender.frameAndSend(2, buf, true, true); | ||
it('does not modify a masked text buffer', function() { | ||
var sender = new Sender({ write: function() {} }); | ||
var sender = new Sender({ write: function() {} }); | ||
var text = 'hi there'; | ||
@@ -42,0 +21,0 @@ sender.frameAndSend(1, text, true, true); |
@@ -290,2 +290,13 @@ var assert = require('assert') | ||
it('before connect can silently fail', function(done) { | ||
server.createServer(++port, function(srv) { | ||
var ws = new WebSocket('ws://localhost:' + port); | ||
ws.on('error', function() {}); | ||
ws.ping('', {}, true); | ||
srv.close(); | ||
ws.terminate(); | ||
done(); | ||
}); | ||
}); | ||
it('without message is successfully transmitted to the server', function(done) { | ||
@@ -338,2 +349,28 @@ server.createServer(++port, function(srv) { | ||
describe('#pong', function() { | ||
it('before connect should fail', function(done) { | ||
server.createServer(++port, function(srv) { | ||
var ws = new WebSocket('ws://localhost:' + port); | ||
ws.on('error', function() {}); | ||
try { | ||
ws.pong(); | ||
} | ||
catch (e) { | ||
srv.close(); | ||
ws.terminate(); | ||
done(); | ||
} | ||
}); | ||
}); | ||
it('before connect can silently fail', function(done) { | ||
server.createServer(++port, function(srv) { | ||
var ws = new WebSocket('ws://localhost:' + port); | ||
ws.on('error', function() {}); | ||
ws.pong('', {}, true); | ||
srv.close(); | ||
ws.terminate(); | ||
done(); | ||
}); | ||
}); | ||
it('without message is successfully transmitted to the server', function(done) { | ||
@@ -340,0 +377,0 @@ server.createServer(++port, function(srv) { |
@@ -505,2 +505,59 @@ var http = require('http') | ||
it('client can be denied asynchronously', function(done) { | ||
var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { | ||
cb(false); | ||
}}, function() { | ||
var options = { | ||
port: port, | ||
host: '127.0.0.1', | ||
headers: { | ||
'Connection': 'Upgrade', | ||
'Upgrade': 'websocket', | ||
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', | ||
'Sec-WebSocket-Version': 8, | ||
'Sec-WebSocket-Origin': 'http://foobar.com' | ||
} | ||
}; | ||
var req = http.request(options); | ||
req.end(); | ||
req.on('response', function(res) { | ||
res.statusCode.should.eql(401); | ||
process.nextTick(function() { | ||
wss.close(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
wss.on('connection', function(ws) { | ||
done(new Error('connection must not be established')); | ||
}); | ||
wss.on('error', function() {}); | ||
}); | ||
it('client can be accepted asynchronously', function(done) { | ||
var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { | ||
cb(true); | ||
}}, function() { | ||
var options = { | ||
port: port, | ||
host: '127.0.0.1', | ||
headers: { | ||
'Connection': 'Upgrade', | ||
'Upgrade': 'websocket', | ||
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', | ||
'Sec-WebSocket-Version': 13, | ||
'Origin': 'http://foobar.com' | ||
} | ||
}; | ||
var req = http.request(options); | ||
req.end(); | ||
}); | ||
wss.on('connection', function(ws) { | ||
ws.terminate(); | ||
wss.close(); | ||
done(); | ||
}); | ||
wss.on('error', function() {}); | ||
}); | ||
it('handles messages passed along with the upgrade request (upgrade head)', function(done) { | ||
@@ -784,2 +841,60 @@ var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { | ||
it('client can be denied asynchronously', function(done) { | ||
var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { | ||
cb(false); | ||
}}, function() { | ||
var options = { | ||
port: port, | ||
host: '127.0.0.1', | ||
headers: { | ||
'Connection': 'Upgrade', | ||
'Upgrade': 'WebSocket', | ||
'Origin': 'http://foobarbaz.com', | ||
'Sec-WebSocket-Key1': '3e6b263 4 17 80', | ||
'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' | ||
} | ||
}; | ||
var req = http.request(options); | ||
req.write('WjN}|M(6'); | ||
req.end(); | ||
req.on('response', function(res) { | ||
res.statusCode.should.eql(401); | ||
process.nextTick(function() { | ||
wss.close(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
wss.on('connection', function(ws) { | ||
done(new Error('connection must not be established')); | ||
}); | ||
wss.on('error', function() {}); | ||
}); | ||
it('client can be accepted asynchronously', function(done) { | ||
var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) { | ||
cb(true); | ||
}}, function() { | ||
var options = { | ||
port: port, | ||
host: '127.0.0.1', | ||
headers: { | ||
'Connection': 'Upgrade', | ||
'Upgrade': 'WebSocket', | ||
'Origin': 'http://foobarbaz.com', | ||
'Sec-WebSocket-Key1': '3e6b263 4 17 80', | ||
'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90' | ||
} | ||
}; | ||
var req = http.request(options); | ||
req.write('WjN}|M(6'); | ||
req.end(); | ||
}); | ||
wss.on('connection', function(ws) { | ||
wss.close(); | ||
done(); | ||
}); | ||
wss.on('error', function() {}); | ||
}); | ||
it('handles messages passed along with the upgrade request (upgrade head)', function(done) { | ||
@@ -786,0 +901,0 @@ var wss = new WebSocketServer({port: ++port, verifyClient: function(o) { |
Sorry, the diff of this file is not supported yet
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
212077
3.97%54
1.89%5524
4.56%5
25%