[Pkg-javascript-commits] [node-ws] 01/06: Imported Upstream version 1.1.0+ds1.e6ddaae4
Ximin Luo
infinity0 at debian.org
Thu May 12 19:52:59 UTC 2016
This is an automated email from the git hooks/post-receive script.
infinity0 pushed a commit to branch master
in repository node-ws.
commit fe0541a93d8677c6bfeb6cf693b9331fd339c834
Author: Ximin Luo <infinity0 at debian.org>
Date: Thu May 12 20:34:41 2016 +0200
Imported Upstream version 1.1.0+ds1.e6ddaae4
---
.gitignore | 1 +
Makefile | 19 +++---
README.md | 17 ++---
SECURITY.md | 33 ++++++++++
doc/ws.md | 4 +-
lib/BufferUtil.fallback.js | 2 +-
lib/PerMessageDeflate.js | 16 ++++-
lib/Receiver.hixie.js | 12 +++-
lib/Receiver.js | 145 +++++++++++++++++++++++++++++++++++--------
lib/Sender.js | 2 +-
lib/Validation.fallback.js | 5 +-
lib/WebSocket.js | 48 ++++++++++----
lib/WebSocketServer.js | 91 +++++++++++++++++++--------
package.json | 4 +-
test/Receiver.test.js | 102 ++++++++++++++++++++++++++++++
test/WebSocket.test.js | 103 +++++++++++++++++++++++++++++-
test/WebSocketServer.test.js | 70 +++++++++++++++++++++
17 files changed, 576 insertions(+), 98 deletions(-)
diff --git a/.gitignore b/.gitignore
index fd77e29..182c7b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,5 +3,6 @@ node_modules
.*.swp
.lock-*
build
+coverage
builderror.log
diff --git a/Makefile b/Makefile
index 00f19fa..94612c5 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,6 @@
ALL_TESTS = $(shell find test/ -name '*.test.js')
ALL_INTEGRATION = $(shell find test/ -name '*.integration.js')
-all:
- node-gyp configure build
-
-clean:
- node-gyp clean
-
run-tests:
@./node_modules/.bin/mocha \
-t 5000 \
@@ -21,12 +15,23 @@ run-integrationtests:
$(TESTFLAGS) \
$(TESTS)
+run-coverage:
+ @./node_modules/.bin/istanbul cover --report html \
+ ./node_modules/.bin/_mocha -- \
+ -t 5000 \
+ -s 6000 \
+ $(TESTFLAGS) \
+ $(TESTS)
+
test:
@$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests
integrationtest:
@$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_INTEGRATION)" run-integrationtests
+coverage:
+ @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_TESTS)" run-coverage
+
benchmark:
@node bench/sender.benchmark.js
@node bench/parser.benchmark.js
@@ -37,4 +42,4 @@ autobahn:
autobahn-server:
@NODE_PATH=lib node test/autobahn-server.js
-.PHONY: test
+.PHONY: test coverage
diff --git a/README.md b/README.md
index 9be2e51..93106d7 100644
--- a/README.md
+++ b/README.md
@@ -31,13 +31,13 @@ compiler is installed on the host system.
- `npm install --save bufferutil`: Improves internal buffer operations which
allows for faster processing of masked WebSocket frames and general buffer
- operations.
+ operations.
- `npm install --save utf-8-validate`: The specification requires validation of
invalid UTF-8 chars, some of these validations could not be done in JavaScript
hence the need for a binary addon. In most cases you will already be
validating the input that you receive for security purposes leading to double
- validation. But if you want to be 100% spec conform and fast validation of UTF-8
- then this module is a must.
+ validation. But if you want to be 100% spec-conforming and have fast
+ validation of UTF-8 then this module is a must.
### Sending and receiving text data
@@ -110,7 +110,7 @@ wss.on('connection', function connection(ws) {
var location = url.parse(ws.upgradeReq.url, true);
// you might use location.query.access_token to authenticate or share sessions
// or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312)
-
+
ws.on('message', function incoming(message) {
console.log('received: %s', message);
});
@@ -161,7 +161,7 @@ catch (e) { /* handle error */ }
```js
var WebSocket = require('ws');
var ws = new WebSocket('ws://echo.websocket.org/', {
- protocolVersion: 8,
+ protocolVersion: 8,
origin: 'http://websocket.org'
});
@@ -183,13 +183,6 @@ ws.on('message', function message(data, flags) {
});
```
-### Browserify users
-When including ws via a browserify bundle, ws returns global.WebSocket which has slightly different API.
-You should use the standard WebSockets API instead.
-
-https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_client_applications#Availability_of_WebSockets
-
-
### Other examples
For a full example with a browser client communicating with a ws server, see the
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..fd8e07b
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,33 @@
+# Security Guidelines
+
+Please contact us directly at **security at 3rd-Eden.com** for any bug that might
+impact the security of this project. Please prefix the subject of your email
+with `[security]` in lowercase and square brackets. Our email filters will
+automatically prevent these messages from being moved to our spam box.
+
+You will receive an acknowledgement of your report within **24 hours**.
+
+All emails that do not include security vulnerabilities will be removed and
+blocked instantly.
+
+## Exceptions
+
+If you do not receive an acknowledgement within the said time frame please give
+us the benefit of the doubt as it's possible that we haven't seen it yet. In
+this case please send us a message **without details** using one of the
+following methods:
+
+- Contact the lead developers of this project on their personal e-mails. You
+ can find the e-mails in the git logs, for example using the following command:
+ `git --no-pager show -s --format='%an <%ae>' <gitsha>` where `<gitsha>` is the
+ SHA1 of their latest commit in the project.
+- Create a GitHub issue stating contact details and the severity of the issue.
+
+Once we have acknowledged receipt of your report and confirmed the bug
+ourselves we will work with you to fix the vulnerability and publicly acknowledge
+your responsible disclosure, if you wish. In addition to that we will report
+all vulnerabilities to the [Node Security Project](https://nodesecurity.io/).
+
+## History
+
+04 Jan 2016: [Buffer vulnerablity](https://github.com/websockets/ws/releases/tag/1.0.1)
diff --git a/doc/ws.md b/doc/ws.md
index 8f23d51..25d08fe 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -66,9 +66,9 @@ If `handleProtocols` is not set then the handshake is accepted regardless the va
If a property is empty then either an offered configuration or a default value is used.
-### server.close()
+### server.close([callback])
-Close the server and terminate all clients
+Close the server and terminate all clients, calls callback when done with an error if one occured.
### server.handleUpgrade(request, socket, upgradeHead, callback)
diff --git a/lib/BufferUtil.fallback.js b/lib/BufferUtil.fallback.js
index 508542c..7abd0d8 100644
--- a/lib/BufferUtil.fallback.js
+++ b/lib/BufferUtil.fallback.js
@@ -4,7 +4,7 @@
* MIT Licensed
*/
-module.exports.BufferUtil = {
+exports.BufferUtil = {
merge: function(mergedBuffer, buffers) {
var offset = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
diff --git a/lib/PerMessageDeflate.js b/lib/PerMessageDeflate.js
index 5324bd8..00a6ea6 100644
--- a/lib/PerMessageDeflate.js
+++ b/lib/PerMessageDeflate.js
@@ -11,7 +11,7 @@ PerMessageDeflate.extensionName = 'permessage-deflate';
* Per-message Compression Extensions implementation
*/
-function PerMessageDeflate(options, isServer) {
+function PerMessageDeflate(options, isServer,maxPayload) {
if (this instanceof PerMessageDeflate === false) {
throw new TypeError("Classes can't be function-called");
}
@@ -21,6 +21,7 @@ function PerMessageDeflate(options, isServer) {
this._inflate = null;
this._deflate = null;
this.params = null;
+ this._maxPayload = maxPayload || 0;
}
/**
@@ -236,6 +237,7 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) {
var self = this;
var buffers = [];
+ var cumulativeBufferLength=0;
this._inflate.on('error', onError).on('data', onData);
this._inflate.write(data);
@@ -253,7 +255,17 @@ PerMessageDeflate.prototype.decompress = function (data, fin, callback) {
}
function onData(data) {
- buffers.push(data);
+ if(self._maxPayload!==undefined && self._maxPayload!==null && self._maxPayload>0){
+ cumulativeBufferLength+=data.length;
+ if(cumulativeBufferLength>self._maxPayload){
+ buffers=[];
+ cleanup();
+ var err={type:1009};
+ callback(err);
+ return;
+ }
+ }
+ buffers.push(data);
}
function cleanup() {
diff --git a/lib/Receiver.hixie.js b/lib/Receiver.hixie.js
index 66bc561..598ccbd 100644
--- a/lib/Receiver.hixie.js
+++ b/lib/Receiver.hixie.js
@@ -47,6 +47,7 @@ module.exports = Receiver;
*/
Receiver.prototype.add = function(data) {
+ if (this.dead) return;
var self = this;
function doAdd() {
if (self.state === EMPTY) {
@@ -153,8 +154,17 @@ Receiver.prototype.parse = function() {
*/
Receiver.prototype.error = function (reason, terminate) {
+ if (this.dead) return;
this.reset();
- this.onerror(reason, terminate);
+ if(typeof reason == 'string'){
+ this.onerror(new Error(reason), terminate);
+ }
+ else if(reason.constructor == Error){
+ this.onerror(reason, terminate);
+ }
+ else{
+ this.onerror(new Error("An error occured"),terminate);
+ }
return this;
};
diff --git a/lib/Receiver.js b/lib/Receiver.js
index b3183bf..0bf29d8 100644
--- a/lib/Receiver.js
+++ b/lib/Receiver.js
@@ -15,10 +15,15 @@ var util = require('util')
* HyBi Receiver implementation
*/
-function Receiver (extensions) {
+function Receiver (extensions,maxPayload) {
if (this instanceof Receiver === false) {
throw new TypeError("Classes can't be function-called");
}
+ if(typeof extensions==='number'){
+ maxPayload=extensions;
+ extensions={};
+ }
+
// memory pool for fragmented messages
var fragmentedPoolPrevUsed = -1;
@@ -39,8 +44,9 @@ function Receiver (extensions) {
Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) :
db.used;
});
-
this.extensions = extensions || {};
+ this.maxPayload = maxPayload || 0;
+ this.currentPayloadLength = 0;
this.state = {
activeFragmentedOperation: null,
lastFragment: false,
@@ -54,6 +60,7 @@ function Receiver (extensions) {
this.expectBuffer = null;
this.expectHandler = null;
this.currentMessage = [];
+ this.currentMessageLength = 0;
this.messageHandlers = [];
this.expectHeader(2, this.processPacket);
this.dead = false;
@@ -76,6 +83,7 @@ module.exports = Receiver;
*/
Receiver.prototype.add = function(data) {
+ if (this.dead) return;
var dataLength = data.length;
if (dataLength == 0) return;
if (this.expectBuffer == null) {
@@ -244,6 +252,7 @@ Receiver.prototype.processPacket = function (data) {
*/
Receiver.prototype.endPacket = function() {
+ if (this.dead) return;
if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true);
else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true);
this.expectOffset = 0;
@@ -253,6 +262,7 @@ Receiver.prototype.endPacket = function() {
// end current fragmented operation
this.state.activeFragmentedOperation = null;
}
+ this.currentPayloadLength = 0;
this.state.lastFragment = false;
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
this.state.masked = false;
@@ -281,7 +291,9 @@ Receiver.prototype.reset = function() {
this.expectHandler = null;
this.overflow = [];
this.currentMessage = [];
+ this.currentMessageLength = 0;
this.messageHandlers = [];
+ this.currentPayloadLength = 0;
};
/**
@@ -297,28 +309,23 @@ Receiver.prototype.unmask = function (mask, buf, binary) {
};
/**
- * Concatenates a list of buffers.
- *
- * @api private
- */
-
-Receiver.prototype.concatBuffers = function(buffers) {
- var length = 0;
- for (var i = 0, l = buffers.length; i < l; ++i) length += buffers[i].length;
- var mergedBuffer = new Buffer(length);
- bufferUtil.merge(mergedBuffer, buffers);
- return mergedBuffer;
-};
-
-/**
* Handles an error
*
* @api private
*/
Receiver.prototype.error = function (reason, protocolErrorCode) {
+ if (this.dead) return;
this.reset();
- this.onerror(reason, protocolErrorCode);
+ if(typeof reason == 'string'){
+ this.onerror(new Error(reason), protocolErrorCode);
+ }
+ else if(reason.constructor == Error){
+ this.onerror(reason, protocolErrorCode);
+ }
+ else{
+ this.onerror(new Error("An error occured"),protocolErrorCode);
+ }
return this;
};
@@ -366,6 +373,27 @@ Receiver.prototype.applyExtensions = function(messageBuffer, fin, compressed, ca
};
/**
+* Checks payload size, disconnects socket when it exceeds maxPayload
+*
+* @api private
+*/
+Receiver.prototype.maxPayloadExceeded = function(length) {
+ if (this.maxPayload=== undefined || this.maxPayload === null || this.maxPayload < 1) {
+ return false;
+ }
+ var fullLength = this.currentPayloadLength + length;
+ if (fullLength < this.maxPayload) {
+ this.currentPayloadLength = fullLength;
+ return false;
+ }
+ this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009);
+ this.messageBuffer=[];
+ this.cleanup();
+
+ return true;
+};
+
+/**
* Buffer utilities
*/
@@ -425,11 +453,20 @@ var opcodes = {
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength < 126) {
+ if (self.maxPayloadExceeded(firstLength)){
+ self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009);
+ return;
+ }
opcodes['1'].getData.call(self, firstLength);
}
else if (firstLength == 126) {
self.expectHeader(2, function(data) {
- opcodes['1'].getData.call(self, readUInt16BE.call(data, 0));
+ var length = readUInt16BE.call(data, 0);
+ if (self.maxPayloadExceeded(length)){
+ self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009);
+ return;
+ }
+ opcodes['1'].getData.call(self, length);
});
}
else if (firstLength == 127) {
@@ -438,6 +475,11 @@ var opcodes = {
self.error('packets with length spanning more than 32 bit is currently not supported', 1008);
return;
}
+ var length = readUInt32BE.call(data, 4);
+ if (self.maxPayloadExceeded(length)){
+ self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009);
+ return;
+ }
opcodes['1'].getData.call(self, readUInt32BE.call(data, 4));
});
}
@@ -464,12 +506,29 @@ var opcodes = {
var state = clone(this.state);
this.messageHandlers.push(function(callback) {
self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) {
- if (err) return self.error(err.message, 1007);
- if (buffer != null) self.currentMessage.push(buffer);
-
+ if (err) {
+ if(err.type===1009){
+ return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009);
+ }
+ return self.error(err.message, 1007);
+ }
+ if (buffer != null) {
+ if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){
+ self.currentMessage.push(buffer);
+ }
+ else{
+ self.currentMessage=null;
+ self.currentMessage = [];
+ self.currentMessageLength = 0;
+ self.error(new Error('Maximum payload exceeded. maxPayload: '+self.maxPayload), 1009);
+ return;
+ }
+ self.currentMessageLength += buffer.length;
+ }
if (state.lastFragment) {
- var messageBuffer = self.concatBuffers(self.currentMessage);
+ var messageBuffer = Buffer.concat(self.currentMessage);
self.currentMessage = [];
+ self.currentMessageLength = 0;
if (!Validation.isValidUTF8(messageBuffer)) {
self.error('invalid utf8 sequence', 1007);
return;
@@ -490,11 +549,20 @@ var opcodes = {
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength < 126) {
+ if (self.maxPayloadExceeded(firstLength)){
+ self.error('Max payload exceeded in compressed text message. Aborting...', 1009);
+ return;
+ }
opcodes['2'].getData.call(self, firstLength);
}
else if (firstLength == 126) {
self.expectHeader(2, function(data) {
- opcodes['2'].getData.call(self, readUInt16BE.call(data, 0));
+ var length = readUInt16BE.call(data, 0);
+ if (self.maxPayloadExceeded(length)){
+ self.error('Max payload exceeded in compressed text message. Aborting...', 1009);
+ return;
+ }
+ opcodes['2'].getData.call(self, length);
});
}
else if (firstLength == 127) {
@@ -503,7 +571,12 @@ var opcodes = {
self.error('packets with length spanning more than 32 bit is currently not supported', 1008);
return;
}
- opcodes['2'].getData.call(self, readUInt32BE.call(data, 4, true));
+ var length = readUInt32BE.call(data, 4, true);
+ if (self.maxPayloadExceeded(length)){
+ self.error('Max payload exceeded in compressed text message. Aborting...', 1009);
+ return;
+ }
+ opcodes['2'].getData.call(self, length);
});
}
},
@@ -529,11 +602,29 @@ var opcodes = {
var state = clone(this.state);
this.messageHandlers.push(function(callback) {
self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) {
- if (err) return self.error(err.message, 1007);
- if (buffer != null) self.currentMessage.push(buffer);
+ if (err) {
+ if(err.type===1009){
+ return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009);
+ }
+ return self.error(err.message, 1007);
+ }
+ if (buffer != null) {
+ if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){
+ self.currentMessage.push(buffer);
+ }
+ else{
+ self.currentMessage=null;
+ self.currentMessage = [];
+ self.currentMessageLength = 0;
+ self.error(new Error('Maximum payload exceeded'), 1009);
+ return;
+ }
+ self.currentMessageLength += buffer.length;
+ }
if (state.lastFragment) {
- var messageBuffer = self.concatBuffers(self.currentMessage);
+ var messageBuffer = Buffer.concat(self.currentMessage);
self.currentMessage = [];
+ self.currentMessageLength = 0;
self.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer});
}
callback();
diff --git a/lib/Sender.js b/lib/Sender.js
index d34061e..6ef2ea2 100644
--- a/lib/Sender.js
+++ b/lib/Sender.js
@@ -197,7 +197,7 @@ Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData,
if (maskData) {
outputBuffer[1] = secondByte | 0x80;
- var mask = this._randomMask || (this._randomMask = getRandomMask());
+ var mask = getRandomMask();
outputBuffer[dataOffset - 4] = mask[0];
outputBuffer[dataOffset - 3] = mask[1];
outputBuffer[dataOffset - 2] = mask[2];
diff --git a/lib/Validation.fallback.js b/lib/Validation.fallback.js
index 2c7c4fd..639b0d3 100644
--- a/lib/Validation.fallback.js
+++ b/lib/Validation.fallback.js
@@ -3,10 +3,9 @@
* Copyright(c) 2011 Einar Otto Stangvik <einaros at gmail.com>
* MIT Licensed
*/
-
-module.exports.Validation = {
+
+exports.Validation = {
isValidUTF8: function(buffer) {
return true;
}
};
-
diff --git a/lib/WebSocket.js b/lib/WebSocket.js
index 4e06c80..bb09e85 100644
--- a/lib/WebSocket.js
+++ b/lib/WebSocket.js
@@ -71,6 +71,7 @@ function WebSocket(address, protocols, options) {
this.readyState = null;
this.supports = {};
this.extensions = {};
+ this._binaryType = 'nodebuffer';
if (Array.isArray(address)) {
initAsServerClient.apply(this, address.concat(options));
@@ -372,6 +373,27 @@ Object.defineProperty(WebSocket.prototype, 'bufferedAmount', {
});
/**
+ * Expose binaryType
+ *
+ * This deviates from the W3C interface since ws doesn't support the required
+ * default "blob" type (instead we define a custom "nodebuffer" type).
+ *
+ * @see http://dev.w3.org/html5/websockets/#the-websocket-interface
+ * @api public
+ */
+Object.defineProperty(WebSocket.prototype, 'binaryType', {
+ get: function get() {
+ return this._binaryType;
+ },
+ set: function set(type) {
+ if (type === 'arraybuffer' || type === 'nodebuffer')
+ this._binaryType = type;
+ else
+ throw new SyntaxError('unsupported binaryType: must be either "nodebuffer" or "arraybuffer"');
+ }
+});
+
+/**
* Emulates the W3C Browser based WebSocket interface using function members.
*
* @see http://dev.w3.org/html5/websockets/#the-websocket-interface
@@ -415,6 +437,8 @@ WebSocket.prototype.addEventListener = function(method, listener) {
var target = this;
function onMessage (data, flags) {
+ if (flags.binary && this.binaryType === 'arraybuffer')
+ data = new Uint8Array(data).buffer;
listener.call(target, new MessageEvent(data, !!flags.binary, target));
}
@@ -523,7 +547,8 @@ function initAsServerClient(req, socket, upgradeHead, options) {
options = new Options({
protocolVersion: protocolVersion,
protocol: null,
- extensions: {}
+ extensions: {},
+ maxPayload: 0
}).merge(options);
// expose state properties
@@ -534,7 +559,7 @@ function initAsServerClient(req, socket, upgradeHead, options) {
this.upgradeReq = req;
this.readyState = WebSocket.CONNECTING;
this._isServer = true;
-
+ this.maxPayload = options.value.maxPayload;
// establish connection
if (options.value.protocolVersion === 'hixie-76') {
establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead);
@@ -770,7 +795,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) {
socket.setTimeout(0);
socket.setNoDelay(true);
- this._receiver = new ReceiverClass(this.extensions);
+ this._receiver = new ReceiverClass(this.extensions,this.maxPayload);
this._socket = socket;
// socket cleanup handlers
@@ -848,7 +873,7 @@ function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) {
self._receiver.onerror = function onerror(reason, errorCode) {
// close the connection when the receiver reports a HyBi error code
self.close(typeof errorCode !== 'undefined' ? errorCode : 1002, '');
- self.emit('error', reason, errorCode);
+ self.emit('error', (reason instanceof Error) ? reason : (new Error(reason)));
};
// finalize the client
@@ -911,21 +936,18 @@ function sendStream(instance, stream, options, cb) {
function cleanupWebsocketResources(error) {
if (this.readyState === WebSocket.CLOSED) return;
- var emitClose = this.readyState !== WebSocket.CONNECTING;
this.readyState = WebSocket.CLOSED;
clearTimeout(this._closeTimer);
this._closeTimer = null;
- if (emitClose) {
- // If the connection was closed abnormally (with an error), or if
- // the close control frame was not received then the close code
- // must default to 1006.
- if (error || !this._closeReceived) {
- this._closeCode = 1006;
- }
- this.emit('close', this._closeCode || 1000, this._closeMessage || '');
+ // If the connection was closed abnormally (with an error), or if
+ // the close control frame was not received then the close code
+ // must default to 1006.
+ if (error || !this._closeReceived) {
+ this._closeCode = 1006;
}
+ this.emit('close', this._closeCode || 1000, this._closeMessage || '');
if (this._socket) {
if (this._ultron) this._ultron.destroy();
diff --git a/lib/WebSocketServer.js b/lib/WebSocketServer.js
index ba0e4c0..476cf71 100644
--- a/lib/WebSocketServer.js
+++ b/lib/WebSocketServer.js
@@ -36,7 +36,8 @@ function WebSocketServer(options, callback) {
noServer: false,
disableHixie: false,
clientTracking: true,
- perMessageDeflate: true
+ perMessageDeflate: true,
+ maxPayload: null
}).merge(options);
if (!options.isDefinedAndNonNull('port') && !options.isDefinedAndNonNull('server') && !options.value.noServer) {
@@ -72,13 +73,15 @@ function WebSocketServer(options, callback) {
this._server._webSocketPaths[options.value.path] = 1;
}
}
- if (this._server) this._server.once('listening', function() { self.emit('listening'); });
+ if (this._server) {
+ this._onceServerListening = function() { self.emit('listening'); };
+ this._server.once('listening', this._onceServerListening);
+ }
if (typeof this._server != 'undefined') {
- this._server.on('error', function(error) {
- self.emit('error', error)
- });
- this._server.on('upgrade', function(req, socket, upgradeHead) {
+ this._onServerError = function(error) { self.emit('error', error) };
+ this._server.on('error', this._onServerError);
+ this._onServerUpgrade = function(req, socket, upgradeHead) {
//copy upgradeHead to avoid retention of large slab buffers used in node core
var head = new Buffer(upgradeHead.length);
upgradeHead.copy(head);
@@ -87,7 +90,8 @@ function WebSocketServer(options, callback) {
self.emit('connection'+req.url, client);
self.emit('connection', client);
});
- });
+ };
+ this._server.on('upgrade', this._onServerUpgrade);
}
this.options = options.value;
@@ -134,6 +138,11 @@ WebSocketServer.prototype.close = function(callback) {
}
}
finally {
+ if (this._server) {
+ this._server.removeListener('listening', this._onceServerListening);
+ this._server.removeListener('error', this._onServerError);
+ this._server.removeListener('upgrade', this._onServerUpgrade);
+ }
delete this._server;
}
if(callback)
@@ -256,7 +265,8 @@ function handleHybiUpgrade(req, socket, upgradeHead, cb) {
var client = new WebSocket([req, socket, upgradeHead], {
protocolVersion: version,
protocol: protocol,
- extensions: extensions
+ extensions: extensions,
+ maxPayload: self.options.maxPayload
});
if (self.options.clientTracking) {
@@ -359,8 +369,42 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) {
var location = ((req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws') + '://' + wshost + req.url
, protocol = req.headers['sec-websocket-protocol'];
+ // build the response header and return a Buffer
+ var buildResponseHeader = function() {
+ 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);
+
+ return new Buffer(headers.concat('', '').join('\r\n'));
+ };
+
+ // send handshake response before receiving the nonce
+ var handshakeResponse = function() {
+
+ socket.setTimeout(0);
+ socket.setNoDelay(true);
+
+ var headerBuffer = buildResponseHeader();
+
+ try {
+ socket.write(headerBuffer, 'binary', function(err) {
+ // remove listener if there was an error
+ if (err) socket.removeListener('data', handler);
+ return;
+ });
+ } catch (e) {
+ try { socket.destroy(); } catch (e) {}
+ return;
+ };
+ };
+
// handshake completion code to run once nonce has been successfully retrieved
- var completeHandshake = function(nonce, rest) {
+ var completeHandshake = function(nonce, rest, headerBuffer) {
// calculate key
var k1 = req.headers['sec-websocket-key1']
, k2 = req.headers['sec-websocket-key2']
@@ -382,20 +426,10 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) {
});
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 {
- // merge header and hash buffer
- var headerBuffer = new Buffer(headers.concat('', '').join('\r\n'));
var hashBuffer = new Buffer(md5.digest('binary'), 'binary');
var handshakeBuffer = new Buffer(headerBuffer.length + hashBuffer.length);
headerBuffer.copy(handshakeBuffer, 0);
@@ -434,11 +468,10 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) {
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);
+ completeHandshake.call(self, nonce, rest, buildResponseHeader());
}
else {
- // nonce not present in upgradeHead, so we must wait for enough data
- // data to arrive before continuing
+ // nonce not present in upgradeHead
var nonce = new Buffer(nonceLength);
upgradeHead.copy(nonce, 0);
var received = upgradeHead.length;
@@ -451,10 +484,17 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) {
if (received == nonceLength) {
socket.removeListener('data', handler);
if (toRead < data.length) rest = data.slice(toRead);
- completeHandshake.call(self, nonce, rest);
+
+ // complete the handshake but send empty buffer for headers since they have already been sent
+ completeHandshake.call(self, nonce, rest, new Buffer(0));
}
}
+
+ // handle additional data as we receive it
socket.on('data', handler);
+
+ // send header response before we have the nonce to fix haproxy buffering
+ handshakeResponse();
}
}
@@ -489,8 +529,9 @@ function handleHixieUpgrade(req, socket, upgradeHead, cb) {
function acceptExtensions(offer) {
var extensions = {};
var options = this.options.perMessageDeflate;
+ var maxPayload = this.options.maxPayload;
if (options && offer[PerMessageDeflate.extensionName]) {
- var perMessageDeflate = new PerMessageDeflate(options !== true ? options : {}, true);
+ var perMessageDeflate = new PerMessageDeflate(options !== true ? options : {}, true, maxPayload);
perMessageDeflate.accept(offer[PerMessageDeflate.extensionName]);
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
}
diff --git a/package.json b/package.json
index 9e974c5..b4968c3 100644
--- a/package.json
+++ b/package.json
@@ -2,8 +2,9 @@
"author": "Einar Otto Stangvik <einaros at gmail.com> (http://2x.io)",
"name": "ws",
"description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455",
- "version": "1.0.1",
+ "version": "1.1.0",
"license": "MIT",
+ "main": "index.js",
"keywords": [
"Hixie",
"HyBi",
@@ -29,6 +30,7 @@
"benchmark": "0.3.x",
"bufferutil": "1.2.x",
"expect.js": "0.3.x",
+ "istanbul": "^0.4.1",
"mocha": "2.3.x",
"should": "8.0.x",
"tinycolor": "0.0.x",
diff --git a/test/Receiver.test.js b/test/Receiver.test.js
index 30fd3b8..9c3343b 100644
--- a/test/Receiver.test.js
+++ b/test/Receiver.test.js
@@ -306,6 +306,108 @@ describe('Receiver', function() {
});
});
});
+ it('will raise an error on a 200kb long masked binary message when maxpayload is 20kb', function() {
+ var p = new Receiver(20480);
+ var length = 200 * 1024;
+ var message = new Buffer(length);
+ for (var i = 0; i < length; ++i) message[i] = i % 256;
+ var originalMessage = getHexStringFromBuffer(message);
+ var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
+
+ var gotError = false;
+ p.error = function(reason,code) {
+ gotError = true;
+ assert.equal(code, 1009);
+ };
+
+ p.add(getBufferFromHexString(packet));
+ gotError.should.be.ok;
+ });
+ it('will raise an error on a 200kb long unmasked binary message when maxpayload is 20kb', function() {
+ var p = new Receiver(20480);
+ var length = 200 * 1024;
+ var message = new Buffer(length);
+ for (var i = 0; i < length; ++i) message[i] = i % 256;
+ var originalMessage = getHexStringFromBuffer(message);
+ var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message);
+
+ var gotError = false;
+ p.error = function(reason,code) {
+ gotError = true;
+ assert.equal(code, 1009);
+ };
+
+ p.add(getBufferFromHexString(packet));
+ gotError.should.be.ok;
+ });
+ it('will raise an error on a compressed message that exceeds maxpayload of 3bytes', function(done) {
+ var perMessageDeflate = new PerMessageDeflate({},false,3);
+ perMessageDeflate.accept([{}]);
+
+ var p = new Receiver({ 'permessage-deflate': perMessageDeflate },3);
+ var buf = new Buffer('Hellooooooooooooooooooooooooooooooooooooooo');
+
+ p.onerror = function(reason,code) {
+ assert.equal(code, 1009);
+ done();
+ };
+
+ perMessageDeflate.compress(buf, true, function(err, compressed) {
+ if (err) return done(err);
+ p.add(new Buffer([0xc1, compressed.length]));
+ p.add(compressed);
+ });
+ });
+ it('will raise an error on a compressed fragment that exceeds maxpayload of 2 bytes', function(done) {
+ var perMessageDeflate = new PerMessageDeflate({},false,2);
+ perMessageDeflate.accept([{}]);
+
+ var p = new Receiver({ 'permessage-deflate': perMessageDeflate },2);
+ var buf1 = new Buffer('fooooooooooooooooooooooooooooooooooooooooooooooooooooooo');
+ var buf2 = new Buffer('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr');
+
+ p.onerror = function(reason,code) {
+ assert.equal(code, 1009);
+ done();
+ };
+
+ perMessageDeflate.compress(buf1, false, function(err, compressed1) {
+ if (err) return done(err);
+ p.add(new Buffer([0x41, compressed1.length]));
+ p.add(compressed1);
+
+ perMessageDeflate.compress(buf2, true, function(err, compressed2) {
+ p.add(new Buffer([0x80, compressed2.length]));
+ p.add(compressed2);
+ });
+ });
+ });
+ it('will not crash if another message is received after receiving a message that exceeds maxpayload', function(done) {
+ var perMessageDeflate = new PerMessageDeflate({},false,2);
+ perMessageDeflate.accept([{}]);
+
+ var p = new Receiver({ 'permessage-deflate': perMessageDeflate },2);
+ var buf1 = new Buffer('fooooooooooooooooooooooooooooooooooooooooooooooooooooooo');
+ var buf2 = new Buffer('baaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr');
+
+ p.onerror = function(reason,code) {
+ assert.equal(code, 1009);
+ };
+
+ perMessageDeflate.compress(buf1, false, function(err, compressed1) {
+ if (err) return done(err);
+ p.add(new Buffer([0x41, compressed1.length]));
+ p.add(compressed1);
+
+ assert.equal(p.onerror,null);
+
+ perMessageDeflate.compress(buf2, true, function(err, compressed2) {
+ p.add(new Buffer([0x80, compressed2.length]));
+ p.add(compressed2);
+ done();
+ });
+ });
+ });
it('can cleanup during consuming data', function(done) {
var perMessageDeflate = new PerMessageDeflate();
perMessageDeflate.accept([{}]);
diff --git a/test/WebSocket.test.js b/test/WebSocket.test.js
index 5b2f4ec..9a69641 100644
--- a/test/WebSocket.test.js
+++ b/test/WebSocket.test.js
@@ -46,6 +46,40 @@ describe('WebSocket', function() {
ws.should.be.an.instanceOf(WebSocket);
done();
});
+
+ it('should emit an error object when the receiver throws an error string', function(done) {
+
+ var wss = new WebSocketServer({port: ++port}, function() {
+
+ var ws = new WebSocket('ws://localhost:' + port);
+
+ ws.on('open', function () {
+ ws._receiver.error('This is an error string', 1002);
+ });
+
+ ws.on('error', function (error) {
+ error.should.be.an.instanceof(Error);
+ done();
+ });
+ });
+ });
+
+ it('should emit an error object when the receiver throws an error object', function(done) {
+
+ var wss = new WebSocketServer({port: ++port}, function() {
+
+ var ws = new WebSocket('ws://localhost:' + port);
+
+ ws.on('open', function () {
+ ws._receiver.error(new Error('This is an error object'), 1002);
+ });
+
+ ws.on('error', function (error) {
+ error.should.be.an.instanceof(Error);
+ done();
+ });
+ });
+ });
});
describe('options', function() {
@@ -398,11 +432,13 @@ describe('WebSocket', function() {
ws.on('open', function() {
assert.fail('connect shouldnt be raised here');
});
- ws.on('close', function() {
- assert.fail('close shouldnt be raised here');
- });
+ var errorCallBackFired = false;
ws.on('error', function() {
+ errorCallBackFired = true;
+ });
+ ws.on('close', function() {
setTimeout(function() {
+ assert.equal(true, errorCallBackFired);
assert.equal(ws.readyState, WebSocket.CLOSED);
done();
}, 50)
@@ -1544,6 +1580,10 @@ describe('WebSocket', function() {
ws.onclose = listener;
ws.onopen = listener;
+ assert.ok(ws.binaryType === 'nodebuffer');
+ ws.binaryType = 'arraybuffer';
+ assert.ok(ws.binaryType === 'arraybuffer');
+
assert.ok(ws.onopen === listener);
assert.ok(ws.onmessage === listener);
assert.ok(ws.onclose === listener);
@@ -1694,6 +1734,63 @@ describe('WebSocket', function() {
client.send('hi')
});
});
+
+ it('should pass binary data as a node.js Buffer by default', function(done) {
+ server.createServer(++port, function(srv) {
+ var ws = new WebSocket('ws://localhost:' + port);
+ var array = new Uint8Array(4096);
+
+ ws.onopen = function() {
+ ws.send(array, {binary: true});
+ };
+ ws.onmessage = function(messageEvent) {
+ assert.ok(messageEvent.binary);
+ assert.ok(ws.binaryType === 'nodebuffer');
+ assert.ok(messageEvent.data instanceof Buffer);
+ ws.terminate();
+ srv.close();
+ done();
+ };
+ });
+ });
+
+ it('should pass an ArrayBuffer for event.data if binaryType = arraybuffer', function(done) {
+ server.createServer(++port, function(srv) {
+ var ws = new WebSocket('ws://localhost:' + port);
+ ws.binaryType = 'arraybuffer';
+ var array = new Uint8Array(4096);
+
+ ws.onopen = function() {
+ ws.send(array, {binary: true});
+ };
+ ws.onmessage = function(messageEvent) {
+ assert.ok(messageEvent.binary);
+ assert.ok(messageEvent.data instanceof ArrayBuffer);
+ ws.terminate();
+ srv.close();
+ done();
+ };
+ });
+ });
+
+ it('should ignore binaryType for text messages', function(done) {
+ server.createServer(++port, function(srv) {
+ var ws = new WebSocket('ws://localhost:' + port);
+ ws.binaryType = 'arraybuffer';
+
+ ws.onopen = function() {
+ ws.send('foobar');
+ };
+ ws.onmessage = function(messageEvent) {
+ assert.ok(!messageEvent.binary);
+ assert.ok(typeof messageEvent.data === 'string');
+ ws.terminate();
+ srv.close();
+ done();
+ };
+ });
+ });
+
});
describe('ssl', function() {
diff --git a/test/WebSocketServer.test.js b/test/WebSocketServer.test.js
index 210a1ad..5dce73c 100644
--- a/test/WebSocketServer.test.js
+++ b/test/WebSocketServer.test.js
@@ -185,6 +185,21 @@ describe('WebSocketServer', function() {
}
});
});
+ it('will not crash when it receives an unhandled opcode', function(done) {
+ var wss = new WebSocketServer({ port: 8080 });
+ wss.on('connection', function connection(ws) {
+ ws.onerror = function(error) {
+ done();
+ };
+ });
+
+ var socket = new WebSocket('ws://127.0.0.1:8080/');
+
+ socket.onopen = function() {
+ socket._socket.write(new Buffer([5]));
+ socket.send('');
+ };
+ });
});
describe('#close', function() {
@@ -232,6 +247,18 @@ describe('WebSocketServer', function() {
});
});
+ it('cleans event handlers on precreated server', function(done) {
+ var srv = http.createServer();
+ srv.listen(++port, function() {
+ var wss = new WebSocketServer({server: srv});
+ wss.close();
+ srv.emit('upgrade');
+ srv.on('error', function() {});
+ srv.emit('error');
+ done()
+ });
+ });
+
it('cleans up websocket data on a precreated server', function(done) {
var srv = http.createServer();
srv.listen(++port, function () {
@@ -315,6 +342,49 @@ describe('WebSocketServer', function() {
});
});
+ describe('#maxpayload #hybiOnly', function() {
+ it('maxpayload is passed on to clients,', function(done) {
+ var _maxPayload = 20480;
+ var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() {
+ wss.clients.length.should.eql(0);
+ var ws = new WebSocket('ws://localhost:' + port);
+ });
+ wss.on('connection', function(client) {
+ wss.clients.length.should.eql(1);
+ wss.clients[0].maxPayload.should.eql(_maxPayload);
+ wss.close();
+ done();
+ });
+ });
+ it('maxpayload is passed on to hybi receivers', function(done) {
+ var _maxPayload = 20480;
+ var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() {
+ wss.clients.length.should.eql(0);
+ var ws = new WebSocket('ws://localhost:' + port);
+ });
+ wss.on('connection', function(client) {
+ wss.clients.length.should.eql(1);
+ wss.clients[0]._receiver.maxPayload.should.eql(_maxPayload);
+ wss.close();
+ done();
+ });
+ });
+ it('maxpayload is passed on to permessage-deflate', function(done) {
+ var PerMessageDeflate = require('../lib/PerMessageDeflate');
+ var _maxPayload = 20480;
+ var wss = new WebSocketServer({port: ++port,maxPayload:_maxPayload, disableHixie: true}, function() {
+ wss.clients.length.should.eql(0);
+ var ws = new WebSocket('ws://localhost:' + port);
+ });
+ wss.on('connection', function(client) {
+ wss.clients.length.should.eql(1);
+ wss.clients[0]._receiver.extensions[PerMessageDeflate.extensionName]._maxPayload.should.eql(_maxPayload);
+ wss.close();
+ done();
+ });
+ });
+ });
+
describe('#handleUpgrade', function() {
it('can be used for a pre-existing server', function (done) {
var srv = http.createServer();
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-ws.git
More information about the Pkg-javascript-commits
mailing list