Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: kazuyukitanimura/ws
base: a21206ac34
...
head fork: kazuyukitanimura/ws
compare: 8c3f6d7fb0
Checking mergeability… Don't worry, you can still create the pull request.
  • 4 commits
  • 4 files changed
  • 0 commit comments
  • 2 contributors
View
21 lib/WebSocket.js
@@ -96,12 +96,11 @@ util.inherits(WebSocket, events.EventEmitter);
*/
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) {
this._readyState = WebSocket.CLOSED;
return;
}
- if (this.readyState != WebSocket.OPEN) throw new Error('not opened');
try {
this._readyState = WebSocket.CLOSING;
this._closeCode = code;
@@ -131,11 +130,15 @@ WebSocket.prototype.pause = function() {
*
* @param {Object} data to be sent to the server
* @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 || {};
if (typeof options.mask == 'undefined') options.mask = !this._isServer;
this._sender.ping(data, options);
@@ -146,11 +149,15 @@ WebSocket.prototype.ping = function(data, options) {
*
* @param {Object} data to be sent to the server
* @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 || {};
if (typeof options.mask == 'undefined') options.mask = !this._isServer;
this._sender.pong(data, options);
@@ -536,7 +543,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) {
});
receiver.on('ping', function(data, flags) {
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);
});
receiver.on('pong', function(data, flags) {
View
208 lib/WebSocketServer.js
@@ -172,19 +172,33 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) {
var origin = version < 13 ?
req.headers['sec-websocket-origin'] :
req.headers['origin'];
+
+ var args = [req, socket, upgradeHead, version, cb];
if (typeof this.options.verifyClient == 'function') {
var info = {
origin: origin,
secure: typeof req.connection.encrypted !== 'undefined',
req: req
};
- 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');
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
var key = req.headers['sec-websocket-key'];
@@ -237,6 +251,96 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) {
return;
}
+ // 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
var location = (socket.encrypted ? 'wss' : 'ws') + '://' + req.headers.host + req.url
, origin = req.headers['origin'];
@@ -246,102 +350,20 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) {
secure: typeof req.connection.encrypted !== 'undefined',
req: req
};
- 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();
}
function abortConnection(socket, code, name) {
View
37 test/WebSocket.test.js
@@ -287,6 +287,17 @@ describe('WebSocket', function() {
});
});
+ 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) {
server.createServer(++port, function(srv) {
var ws = new WebSocket('ws://localhost:' + port);
@@ -334,6 +345,32 @@ describe('WebSocket', function() {
});
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) {
server.createServer(++port, function(srv) {
var ws = new WebSocket('ws://localhost:' + port);
View
115 test/WebSocketServer.test.js
@@ -502,6 +502,63 @@ describe('WebSocketServer', function() {
});
});
+ it('client can be denied asynchronously', function(done) {
+ var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) {
+ cb(false);
+ }}, function() {
+ var options = {
+ port: port,
+ host: '127.0.0.1',
+ headers: {
+ 'Connection': 'Upgrade',
+ 'Upgrade': 'websocket',
+ '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) {
var wss = new WebSocketServer({port: ++port, verifyClient: function(o) {
return true;
@@ -780,6 +837,64 @@ describe('WebSocketServer', function() {
wss.on('error', function() {});
});
+ it('client can be denied asynchronously', function(done) {
+ var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) {
+ cb(false);
+ }}, function() {
+ var options = {
+ port: port,
+ host: '127.0.0.1',
+ headers: {
+ 'Connection': 'Upgrade',
+ 'Upgrade': 'WebSocket',
+ 'Origin': 'http://foobarbaz.com',
+ 'Sec-WebSocket-Key1': '3e6b263 4 17 80',
+ 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90'
+ }
+ };
+ var req = http.request(options);
+ req.write('WjN}|M(6');
+ req.end();
+ req.on('response', function(res) {
+ res.statusCode.should.eql(401);
+ process.nextTick(function() {
+ wss.close();
+ done();
+ });
+ });
+ });
+ wss.on('connection', function(ws) {
+ done(new Error('connection must not be established'));
+ });
+ wss.on('error', function() {});
+ });
+
+ it('client can be accepted asynchronously', function(done) {
+ var wss = new WebSocketServer({port: ++port, verifyClient: function(o, cb) {
+ cb(true);
+ }}, function() {
+ var options = {
+ port: port,
+ host: '127.0.0.1',
+ headers: {
+ 'Connection': 'Upgrade',
+ 'Upgrade': 'WebSocket',
+ 'Origin': 'http://foobarbaz.com',
+ 'Sec-WebSocket-Key1': '3e6b263 4 17 80',
+ 'Sec-WebSocket-Key2': '17 9 G`ZD9 2 2b 7X 3 /r90'
+ }
+ };
+ var req = http.request(options);
+ req.write('WjN}|M(6');
+ req.end();
+ });
+ wss.on('connection', function(ws) {
+ wss.close();
+ done();
+ });
+ wss.on('error', function() {});
+ });
+
it('handles messages passed along with the upgrade request (upgrade head)', function(done) {
var wss = new WebSocketServer({port: ++port, verifyClient: function(o) {
return true;

No commit comments for this range

Something went wrong with that request. Please try again.