diff options
author | Yaroslav De La Peña Smirnov <yaros.rus_89@live.com.mx> | 2017-11-29 11:44:34 +0300 |
---|---|---|
committer | Yaroslav De La Peña Smirnov <yaros.rus_89@live.com.mx> | 2017-11-29 11:44:34 +0300 |
commit | 67fdec20726e48ba3a934cb25bb30d47ec4a4f29 (patch) | |
tree | 37fd9f4f0b0c20103e1646fc83021e4765de3680 /node_modules/ws/lib/PerMessageDeflate.js | |
download | spanish-checkers-67fdec20726e48ba3a934cb25bb30d47ec4a4f29.tar.gz spanish-checkers-67fdec20726e48ba3a934cb25bb30d47ec4a4f29.zip |
Initial commit, version 0.5.3
Diffstat (limited to 'node_modules/ws/lib/PerMessageDeflate.js')
-rw-r--r-- | node_modules/ws/lib/PerMessageDeflate.js | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/node_modules/ws/lib/PerMessageDeflate.js b/node_modules/ws/lib/PerMessageDeflate.js new file mode 100644 index 0000000..c1a1d3c --- /dev/null +++ b/node_modules/ws/lib/PerMessageDeflate.js @@ -0,0 +1,384 @@ +'use strict'; + +const safeBuffer = require('safe-buffer'); +const zlib = require('zlib'); + +const bufferUtil = require('./BufferUtil'); + +const Buffer = safeBuffer.Buffer; + +const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; +const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); +const EMPTY_BLOCK = Buffer.from([0x00]); +const DEFAULT_WINDOW_BITS = 15; +const DEFAULT_MEM_LEVEL = 8; + +/** + * Per-message Deflate implementation. + */ +class PerMessageDeflate { + constructor (options, isServer, maxPayload) { + this._options = options || {}; + this._isServer = !!isServer; + this._inflate = null; + this._deflate = null; + this.params = null; + this._maxPayload = maxPayload || 0; + this.threshold = this._options.threshold === undefined ? 1024 : this._options.threshold; + } + + static get extensionName () { + return 'permessage-deflate'; + } + + /** + * Create extension parameters offer. + * + * @return {Object} Extension parameters + * @public + */ + offer () { + const params = {}; + + if (this._options.serverNoContextTakeover) { + params.server_no_context_takeover = true; + } + if (this._options.clientNoContextTakeover) { + params.client_no_context_takeover = true; + } + if (this._options.serverMaxWindowBits) { + params.server_max_window_bits = this._options.serverMaxWindowBits; + } + if (this._options.clientMaxWindowBits) { + params.client_max_window_bits = this._options.clientMaxWindowBits; + } else if (this._options.clientMaxWindowBits == null) { + params.client_max_window_bits = true; + } + + return params; + } + + /** + * Accept extension offer. + * + * @param {Array} paramsList Extension parameters + * @return {Object} Accepted configuration + * @public + */ + accept (paramsList) { + paramsList = this.normalizeParams(paramsList); + + var params; + if (this._isServer) { + params = this.acceptAsServer(paramsList); + } else { + params = this.acceptAsClient(paramsList); + } + + this.params = params; + return params; + } + + /** + * Releases all resources used by the extension. + * + * @public + */ + cleanup () { + if (this._inflate) { + if (this._inflate.writeInProgress) { + this._inflate.pendingClose = true; + } else { + this._inflate.close(); + this._inflate = null; + } + } + if (this._deflate) { + if (this._deflate.writeInProgress) { + this._deflate.pendingClose = true; + } else { + this._deflate.close(); + this._deflate = null; + } + } + } + + /** + * Accept extension offer from client. + * + * @param {Array} paramsList Extension parameters + * @return {Object} Accepted configuration + * @private + */ + acceptAsServer (paramsList) { + const accepted = {}; + const result = paramsList.some((params) => { + if (( + this._options.serverNoContextTakeover === false && + params.server_no_context_takeover + ) || ( + this._options.serverMaxWindowBits === false && + params.server_max_window_bits + ) || ( + typeof this._options.serverMaxWindowBits === 'number' && + typeof params.server_max_window_bits === 'number' && + this._options.serverMaxWindowBits > params.server_max_window_bits + ) || ( + typeof this._options.clientMaxWindowBits === 'number' && + !params.client_max_window_bits + )) { + return; + } + + if ( + this._options.serverNoContextTakeover || + params.server_no_context_takeover + ) { + accepted.server_no_context_takeover = true; + } + if (this._options.clientNoContextTakeover) { + accepted.client_no_context_takeover = true; + } + if ( + this._options.clientNoContextTakeover !== false && + params.client_no_context_takeover + ) { + accepted.client_no_context_takeover = true; + } + if (typeof this._options.serverMaxWindowBits === 'number') { + accepted.server_max_window_bits = this._options.serverMaxWindowBits; + } else if (typeof params.server_max_window_bits === 'number') { + accepted.server_max_window_bits = params.server_max_window_bits; + } + if (typeof this._options.clientMaxWindowBits === 'number') { + accepted.client_max_window_bits = this._options.clientMaxWindowBits; + } else if ( + this._options.clientMaxWindowBits !== false && + typeof params.client_max_window_bits === 'number' + ) { + accepted.client_max_window_bits = params.client_max_window_bits; + } + return true; + }); + + if (!result) throw new Error(`Doesn't support the offered configuration`); + + return accepted; + } + + /** + * Accept extension response from server. + * + * @param {Array} paramsList Extension parameters + * @return {Object} Accepted configuration + * @private + */ + acceptAsClient (paramsList) { + const params = paramsList[0]; + + 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.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' && ( + !params.client_max_window_bits || + params.client_max_window_bits > this._options.clientMaxWindowBits + )) { + throw new Error('Invalid value for "client_max_window_bits"'); + } + } + + return params; + } + + /** + * Normalize extensions parameters. + * + * @param {Array} paramsList Extension parameters + * @return {Array} Normalized extensions parameters + * @private + */ + normalizeParams (paramsList) { + return paramsList.map((params) => { + Object.keys(params).forEach((key) => { + var value = params[key]; + if (value.length > 1) { + throw new Error(`Multiple extension parameters for ${key}`); + } + + value = value[0]; + + switch (key) { + case 'server_no_context_takeover': + case 'client_no_context_takeover': + if (value !== true) { + throw new Error(`invalid extension parameter value for ${key} (${value})`); + } + params[key] = true; + break; + case 'server_max_window_bits': + case 'client_max_window_bits': + if (typeof value === 'string') { + value = parseInt(value, 10); + if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { + throw new Error(`invalid extension parameter value for ${key} (${value})`); + } + } + if (!this._isServer && value === true) { + throw new Error(`Missing extension parameter value for ${key}`); + } + params[key] = value; + break; + default: + throw new Error(`Not defined extension parameter (${key})`); + } + }); + return params; + }); + } + + /** + * Decompress data. + * + * @param {Buffer} data Compressed data + * @param {Boolean} fin Specifies whether or not this is the last fragment + * @param {Function} callback Callback + * @public + */ + decompress (data, fin, callback) { + const endpoint = this._isServer ? 'client' : 'server'; + + if (!this._inflate) { + const maxWindowBits = this.params[`${endpoint}_max_window_bits`]; + this._inflate = zlib.createInflateRaw({ + windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS + }); + } + this._inflate.writeInProgress = true; + + var totalLength = 0; + const buffers = []; + var err; + + const onData = (data) => { + totalLength += data.length; + if (this._maxPayload < 1 || totalLength <= this._maxPayload) { + return buffers.push(data); + } + + err = new Error('max payload size exceeded'); + err.closeCode = 1009; + this._inflate.reset(); + }; + + const onError = (err) => { + cleanup(); + callback(err); + }; + + const cleanup = () => { + if (!this._inflate) return; + + this._inflate.removeListener('error', onError); + this._inflate.removeListener('data', onData); + this._inflate.writeInProgress = false; + + if ( + (fin && this.params[`${endpoint}_no_context_takeover`]) || + this._inflate.pendingClose + ) { + this._inflate.close(); + this._inflate = null; + } + }; + + this._inflate.on('error', onError).on('data', onData); + this._inflate.write(data); + if (fin) this._inflate.write(TRAILER); + + this._inflate.flush(() => { + cleanup(); + if (err) callback(err); + else callback(null, bufferUtil.concat(buffers, totalLength)); + }); + } + + /** + * Compress data. + * + * @param {Buffer} data Data to compress + * @param {Boolean} fin Specifies whether or not this is the last fragment + * @param {Function} callback Callback + * @public + */ + compress (data, fin, callback) { + if (!data || data.length === 0) { + process.nextTick(callback, null, EMPTY_BLOCK); + return; + } + + const endpoint = this._isServer ? 'server' : 'client'; + + if (!this._deflate) { + const maxWindowBits = this.params[`${endpoint}_max_window_bits`]; + this._deflate = zlib.createDeflateRaw({ + flush: zlib.Z_SYNC_FLUSH, + windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS, + memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL + }); + } + this._deflate.writeInProgress = true; + + var totalLength = 0; + const buffers = []; + + const onData = (data) => { + totalLength += data.length; + buffers.push(data); + }; + + const onError = (err) => { + cleanup(); + callback(err); + }; + + const cleanup = () => { + if (!this._deflate) return; + + this._deflate.removeListener('error', onError); + this._deflate.removeListener('data', onData); + this._deflate.writeInProgress = false; + + if ( + (fin && this.params[`${endpoint}_no_context_takeover`]) || + this._deflate.pendingClose + ) { + this._deflate.close(); + this._deflate = null; + } + }; + + this._deflate.on('error', onError).on('data', onData); + this._deflate.write(data); + this._deflate.flush(zlib.Z_SYNC_FLUSH, () => { + cleanup(); + var data = bufferUtil.concat(buffers, totalLength); + if (fin) data = data.slice(0, data.length - 4); + callback(null, data); + }); + } +} + +module.exports = PerMessageDeflate; |