Socket
Socket
Sign inDemoInstall

ws

Package Overview
Dependencies
Maintainers
4
Versions
169
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ws - npm Package Compare versions

Comparing version 3.3.1 to 3.3.2

201

lib/Extensions.js
'use strict';
//
// Allowed token characters:
//
// '!', '#', '$', '%', '&', ''', '*', '+', '-',
// '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~'
//
// tokenChars[32] === 0 // ' '
// tokenChars[33] === 1 // '!'
// tokenChars[34] === 0 // '"'
// ...
//
const tokenChars = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127
];
/**
* Parse the `Sec-WebSocket-Extensions` header into an object.
* Adds an offer to the map of extension offers or a parameter to the map of
* parameters.
*
* @param {String} value field value of the header
* @param {Object} dest The map of extension offers or parameters
* @param {String} name The extension or parameter name
* @param {(Object|Boolean|String)} elem The extension parameters or the
* parameter value
* @private
*/
function push (dest, name, elem) {
if (Object.prototype.hasOwnProperty.call(dest, name)) dest[name].push(elem);
else dest[name] = [elem];
}
/**
* Parses the `Sec-WebSocket-Extensions` header into an object.
*
* @param {String} header The field value of the header
* @return {Object} The parsed object
* @public
*/
const parse = (value) => {
value = value || '';
function parse (header) {
const offers = {};
const extensions = {};
if (header === undefined || header === '') return offers;
value.split(',').forEach((v) => {
const params = v.split(';');
const token = params.shift().trim();
var params = {};
var mustUnescape = false;
var isEscaping = false;
var inQuotes = false;
var extensionName;
var paramName;
var start = -1;
var end = -1;
if (extensions[token] === undefined) {
extensions[token] = [];
} else if (!extensions.hasOwnProperty(token)) {
return;
}
for (var i = 0; i < header.length; i++) {
const code = header.charCodeAt(i);
const parsedParams = {};
if (extensionName === undefined) {
if (end === -1 && tokenChars[code] === 1) {
if (start === -1) start = i;
} else if (code === 0x20/* ' ' */|| code === 0x09/* '\t' */) {
if (end === -1 && start !== -1) end = i;
} else if (code === 0x3b/* ';' */ || code === 0x2c/* ',' */) {
if (start === -1) throw new Error(`unexpected character at index ${i}`);
params.forEach((param) => {
const parts = param.trim().split('=');
const key = parts[0];
var value = parts[1];
if (end === -1) end = i;
const name = header.slice(start, end);
if (code === 0x2c) {
push(offers, name, params);
params = {};
} else {
extensionName = name;
}
if (value === undefined) {
value = true;
start = end = -1;
} else {
// unquote value
if (value[0] === '"') {
value = value.slice(1);
throw new Error(`unexpected character at index ${i}`);
}
} else if (paramName === undefined) {
if (end === -1 && tokenChars[code] === 1) {
if (start === -1) start = i;
} else if (code === 0x20 || code === 0x09) {
if (end === -1 && start !== -1) end = i;
} else if (code === 0x3b || code === 0x2c) {
if (start === -1) throw new Error(`unexpected character at index ${i}`);
if (end === -1) end = i;
push(params, header.slice(start, end), true);
if (code === 0x2c) {
push(offers, extensionName, params);
params = {};
extensionName = undefined;
}
if (value[value.length - 1] === '"') {
value = value.slice(0, value.length - 1);
start = end = -1;
} else if (code === 0x3d/* '=' */&& start !== -1 && end === -1) {
paramName = header.slice(start, i);
start = end = -1;
} else {
throw new Error(`unexpected character at index ${i}`);
}
} else {
//
// The value of a quoted-string after unescaping must conform to the
// token ABNF, so only token characters are valid.
// Ref: https://tools.ietf.org/html/rfc6455#section-9.1
//
if (isEscaping) {
if (tokenChars[code] !== 1) {
throw new Error(`unexpected character at index ${i}`);
}
}
if (start === -1) start = i;
else if (!mustUnescape) mustUnescape = true;
isEscaping = false;
} else if (inQuotes) {
if (tokenChars[code] === 1) {
if (start === -1) start = i;
} else if (code === 0x22/* '"' */ && start !== -1) {
inQuotes = false;
end = i;
} else if (code === 0x5c/* '\' */) {
isEscaping = true;
} else {
throw new Error(`unexpected character at index ${i}`);
}
} else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) {
inQuotes = true;
} else if (end === -1 && tokenChars[code] === 1) {
if (start === -1) start = i;
} else if (start !== -1 && (code === 0x20 || code === 0x09)) {
if (end === -1) end = i;
} else if (code === 0x3b || code === 0x2c) {
if (start === -1) throw new Error(`unexpected character at index ${i}`);
if (parsedParams[key] === undefined) {
parsedParams[key] = [value];
} else if (parsedParams.hasOwnProperty(key)) {
parsedParams[key].push(value);
if (end === -1) end = i;
var value = header.slice(start, end);
if (mustUnescape) {
value = value.replace(/\\/g, '');
mustUnescape = false;
}
push(params, paramName, value);
if (code === 0x2c) {
push(offers, extensionName, params);
params = {};
extensionName = undefined;
}
paramName = undefined;
start = end = -1;
} else {
throw new Error(`unexpected character at index ${i}`);
}
});
}
}
extensions[token].push(parsedParams);
});
if (start === -1 || inQuotes) throw new Error('unexpected end of input');
return extensions;
};
if (end === -1) end = i;
const token = header.slice(start, end);
if (extensionName === undefined) {
push(offers, token, {});
} else {
if (paramName === undefined) {
push(params, token, true);
} else if (mustUnescape) {
push(params, paramName, token.replace(/\\/g, ''));
} else {
push(params, paramName, token);
}
push(offers, extensionName, params);
}
return offers;
}
/**
* Serialize a parsed `Sec-WebSocket-Extensions` header to a string.
* Serializes a parsed `Sec-WebSocket-Extensions` header to a string.
*

@@ -64,3 +189,3 @@ * @param {Object} value The object to format

*/
const format = (value) => {
function format (value) {
return Object.keys(value).map((token) => {

@@ -77,4 +202,4 @@ var paramsList = value[token];

}).join(', ');
};
}
module.exports = { format, parse };

65

lib/PerMessageDeflate.js

@@ -22,2 +22,3 @@ 'use strict';

//
// We limit zlib concurrency, which prevents severe memory fragmentation

@@ -27,10 +28,32 @@ // as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913

//
// Intentionally global; it's the global thread pool that's
// an issue.
// Intentionally global; it's the global thread pool that's an issue.
//
let zlibLimiter;
/**
* Per-message Deflate implementation.
* permessage-deflate implementation.
*/
class PerMessageDeflate {
/**
* Creates a PerMessageDeflate instance.
*
* @param {Object} options Configuration options
* @param {Boolean} options.serverNoContextTakeover Request/accept disabling
* of server context takeover
* @param {Boolean} options.clientNoContextTakeover Advertise/acknowledge
* disabling of client context takeover
* @param {(Boolean|Number)} options.serverMaxWindowBits Request/confirm the
* use of a custom server window size
* @param {(Boolean|Number)} options.clientMaxWindowBits Advertise support
* for, or request, a custom client window size
* @param {Number} options.level The value of zlib's `level` param
* @param {Number} options.memLevel The value of zlib's `memLevel` param
* @param {Number} options.threshold Size (in bytes) below which messages
* should not be compressed
* @param {Number} options.concurrencyLimit The number of concurrent calls to
* zlib
* @param {Boolean} isServer Create the instance in either server or client
* mode
* @param {Number} maxPayload The maximum allowed message length
*/
constructor (options, isServer, maxPayload) {

@@ -56,2 +79,5 @@ this._maxPayload = maxPayload | 0;

/**
* @type {String}
*/
static get extensionName () {

@@ -201,24 +227,17 @@ return 'permessage-deflate';

if (this._options.clientNoContextTakeover != null) {
if (
this._options.clientNoContextTakeover === false &&
params.client_no_context_takeover
) {
throw new Error('Invalid value for "client_no_context_takeover"');
}
if (
this._options.clientNoContextTakeover === false &&
params.client_no_context_takeover
) {
throw new Error('Invalid value for "client_no_context_takeover"');
}
if (this._options.clientMaxWindowBits != null) {
if (
this._options.clientMaxWindowBits === false &&
params.client_max_window_bits
) {
throw new Error('Invalid value for "client_max_window_bits"');
}
if (
typeof this._options.clientMaxWindowBits === 'number' &&
if (
(typeof this._options.clientMaxWindowBits === 'number' &&
(!params.client_max_window_bits ||
params.client_max_window_bits > this._options.clientMaxWindowBits)
) {
throw new Error('Invalid value for "client_max_window_bits"');
}
params.client_max_window_bits > this._options.clientMaxWindowBits)) ||
(this._options.clientMaxWindowBits === false &&
params.client_max_window_bits)
) {
throw new Error('Invalid value for "client_max_window_bits"');
}

@@ -225,0 +244,0 @@

@@ -530,17 +530,4 @@ /*!

const httpObj = isSecure ? https : http;
//
// Prepare extensions.
//
const extensionsOffer = {};
var perMessageDeflate;
if (options.perMessageDeflate) {
perMessageDeflate = new PerMessageDeflate(
options.perMessageDeflate !== true ? options.perMessageDeflate : {},
false
);
extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer();
}
const requestOptions = {

@@ -559,4 +546,10 @@ port: serverUrl.port || (isSecure ? 443 : 80),

if (options.headers) Object.assign(requestOptions.headers, options.headers);
if (Object.keys(extensionsOffer).length) {
requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer);
if (options.perMessageDeflate) {
perMessageDeflate = new PerMessageDeflate(
options.perMessageDeflate !== true ? options.perMessageDeflate : {},
false
);
requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format({
[PerMessageDeflate.extensionName]: perMessageDeflate.offer()
});
}

@@ -697,14 +690,19 @@ if (options.protocol) {

const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']);
if (perMessageDeflate) {
try {
const serverExtensions = Extensions.parse(
res.headers['sec-websocket-extensions']
);
if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) {
try {
perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]);
if (serverExtensions[PerMessageDeflate.extensionName]) {
perMessageDeflate.accept(
serverExtensions[PerMessageDeflate.extensionName]
);
this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
}
} catch (err) {
socket.destroy();
this.emit('error', new Error('invalid extension parameter'));
this.emit('error', new Error('invalid Sec-WebSocket-Extensions header'));
return this.finalize(true);
}
this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
}

@@ -711,0 +709,0 @@

@@ -93,2 +93,3 @@ /*!

if (options.perMessageDeflate === true) options.perMessageDeflate = {};
if (options.clientTracking) this.clients = new Set();

@@ -155,2 +156,3 @@ this.options = options;

const version = +req.headers['sec-websocket-version'];
const extensions = {};

@@ -165,2 +167,23 @@ if (

if (this.options.perMessageDeflate) {
const perMessageDeflate = new PerMessageDeflate(
this.options.perMessageDeflate,
true,
this.options.maxPayload
);
try {
const offers = Extensions.parse(
req.headers['sec-websocket-extensions']
);
if (offers[PerMessageDeflate.extensionName]) {
perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
}
} catch (err) {
return abortConnection(socket, 400);
}
}
var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */);

@@ -192,11 +215,19 @@

this.completeUpgrade(protocol, version, req, socket, head, cb);
this.completeUpgrade(
protocol,
extensions,
version,
req,
socket,
head,
cb
);
});
return;
} else if (!this.options.verifyClient(info)) {
return abortConnection(socket, 401);
}
if (!this.options.verifyClient(info)) return abortConnection(socket, 401);
}
this.completeUpgrade(protocol, version, req, socket, head, cb);
this.completeUpgrade(protocol, extensions, version, req, socket, head, cb);
}

@@ -208,2 +239,3 @@

* @param {String} protocol The chosen subprotocol
* @param {Object} extensions The accepted extensions
* @param {Number} version The WebSocket protocol version

@@ -216,3 +248,3 @@ * @param {http.IncomingMessage} req The request object

*/
completeUpgrade (protocol, version, req, socket, head, cb) {
completeUpgrade (protocol, extensions, version, req, socket, head, cb) {
//

@@ -235,23 +267,10 @@ // Destroy the socket if the client has already sent a FIN packet.

if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
const offer = Extensions.parse(req.headers['sec-websocket-extensions']);
var extensions;
try {
extensions = acceptExtensions(this.options, offer);
} catch (err) {
return abortConnection(socket, 400);
if (extensions[PerMessageDeflate.extensionName]) {
const params = extensions[PerMessageDeflate.extensionName].params;
const value = Extensions.format({
[PerMessageDeflate.extensionName]: [params]
});
headers.push(`Sec-WebSocket-Extensions: ${value}`);
}
const props = Object.keys(extensions);
if (props.length) {
const serverExtensions = props.reduce((obj, key) => {
obj[key] = [extensions[key].params];
return obj;
}, {});
headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`);
}
//

@@ -262,3 +281,3 @@ // Allow external modification/inspection of handshake headers.

socket.write(headers.concat('', '').join('\r\n'));
socket.write(headers.concat('\r\n').join('\r\n'));

@@ -294,28 +313,2 @@ const client = new WebSocket([socket, head], null, {

/**
* Accept WebSocket extensions.
*
* @param {Object} options The `WebSocketServer` configuration options
* @param {Object} offer The parsed value of the `sec-websocket-extensions` header
* @return {Object} Accepted extensions
* @private
*/
function acceptExtensions (options, offer) {
const pmd = options.perMessageDeflate;
const extensions = {};
if (pmd && offer[PerMessageDeflate.extensionName]) {
const perMessageDeflate = new PerMessageDeflate(
pmd !== true ? pmd : {},
true,
options.maxPayload
);
perMessageDeflate.accept(offer[PerMessageDeflate.extensionName]);
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
}
return extensions;
}
/**
* Close the connection when preconditions are not fulfilled.

@@ -322,0 +315,0 @@ *

{
"name": "ws",
"version": "3.3.1",
"version": "3.3.2",
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",

@@ -36,3 +36,3 @@ "keywords": [

"bufferutil": "~3.0.0",
"eslint": "~4.10.0",
"eslint": "~4.11.0",
"eslint-config-standard": "~10.2.0",

@@ -39,0 +39,0 @@ "eslint-plugin-import": "~2.8.0",

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