From 6aeb555cc4b64c500393ddf5ab7941cf0bc9973c Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 1 Jun 2017 11:23:59 -0700 Subject: [PATCH] zlib: revert back to Functions Using ES6 Classes broke userland code. Revert back to functions. PR-URL: https://github.com/nodejs/node/pull/13374 Fixes: https://github.com/nodejs/node/issues/13358 Ref: https://github.com/nodejs/node/pull/13370 Reviewed-By: Anna Henningsen Reviewed-By: Refael Ackermann Reviewed-By: Daijiro Wachi Reviewed-By: Matteo Collina --- lib/zlib.js | 595 +++++++++--------- .../test-zlib-deflate-raw-inherits.js | 27 + 2 files changed, 329 insertions(+), 293 deletions(-) create mode 100644 test/parallel/test-zlib-deflate-raw-inherits.js diff --git a/lib/zlib.js b/lib/zlib.js index 3024909f516779..a25901ac6ef16d 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -22,7 +22,6 @@ 'use strict'; const Buffer = require('buffer').Buffer; -const internalUtil = require('internal/util'); const Transform = require('_stream_transform'); const binding = process.binding('zlib'); const assert = require('assert').ok; @@ -31,7 +30,7 @@ const kRangeErrorMessage = 'Cannot create final Buffer. It would be larger ' + `than 0x${kMaxLength.toString(16)} bytes`; const constants = process.binding('constants').zlib; -const createClassWrapper = internalUtil.createClassWrapper; +const { inherits } = require('util'); // translation table for return codes. const codes = { @@ -170,319 +169,322 @@ function flushCallback(level, strategy, callback) { // This thing manages the queue of requests, and returns // true or false if there is anything in the queue when // you call the .write() method. -class Zlib extends Transform { - constructor(opts, mode) { - opts = opts || {}; - super(opts); +function Zlib(opts, mode) { + opts = opts || {}; + Transform.call(this, opts); - this.bytesRead = 0; + this.bytesRead = 0; - this._opts = opts; - this._chunkSize = opts.chunkSize || constants.Z_DEFAULT_CHUNK; + this._opts = opts; + this._chunkSize = opts.chunkSize || constants.Z_DEFAULT_CHUNK; - if (opts.flush && isInvalidFlushFlag(opts.flush)) { - throw new RangeError('Invalid flush flag: ' + opts.flush); - } - if (opts.finishFlush && isInvalidFlushFlag(opts.finishFlush)) { - throw new RangeError('Invalid flush flag: ' + opts.finishFlush); - } + if (opts.flush && isInvalidFlushFlag(opts.flush)) { + throw new RangeError('Invalid flush flag: ' + opts.flush); + } + if (opts.finishFlush && isInvalidFlushFlag(opts.finishFlush)) { + throw new RangeError('Invalid flush flag: ' + opts.finishFlush); + } - this._flushFlag = opts.flush || constants.Z_NO_FLUSH; - this._finishFlushFlag = opts.finishFlush !== undefined ? - opts.finishFlush : constants.Z_FINISH; + this._flushFlag = opts.flush || constants.Z_NO_FLUSH; + this._finishFlushFlag = opts.finishFlush !== undefined ? + opts.finishFlush : constants.Z_FINISH; - if (opts.chunkSize !== undefined) { - if (opts.chunkSize < constants.Z_MIN_CHUNK) { - throw new RangeError('Invalid chunk size: ' + opts.chunkSize); - } + if (opts.chunkSize !== undefined) { + if (opts.chunkSize < constants.Z_MIN_CHUNK) { + throw new RangeError('Invalid chunk size: ' + opts.chunkSize); } + } - if (opts.windowBits !== undefined) { - if (opts.windowBits < constants.Z_MIN_WINDOWBITS || - opts.windowBits > constants.Z_MAX_WINDOWBITS) { - throw new RangeError('Invalid windowBits: ' + opts.windowBits); - } + if (opts.windowBits !== undefined) { + if (opts.windowBits < constants.Z_MIN_WINDOWBITS || + opts.windowBits > constants.Z_MAX_WINDOWBITS) { + throw new RangeError('Invalid windowBits: ' + opts.windowBits); } + } - if (opts.level !== undefined) { - if (opts.level < constants.Z_MIN_LEVEL || - opts.level > constants.Z_MAX_LEVEL) { - throw new RangeError('Invalid compression level: ' + opts.level); - } + if (opts.level !== undefined) { + if (opts.level < constants.Z_MIN_LEVEL || + opts.level > constants.Z_MAX_LEVEL) { + throw new RangeError('Invalid compression level: ' + opts.level); } + } - if (opts.memLevel !== undefined) { - if (opts.memLevel < constants.Z_MIN_MEMLEVEL || - opts.memLevel > constants.Z_MAX_MEMLEVEL) { - throw new RangeError('Invalid memLevel: ' + opts.memLevel); - } + if (opts.memLevel !== undefined) { + if (opts.memLevel < constants.Z_MIN_MEMLEVEL || + opts.memLevel > constants.Z_MAX_MEMLEVEL) { + throw new RangeError('Invalid memLevel: ' + opts.memLevel); } + } - if (opts.strategy !== undefined && isInvalidStrategy(opts.strategy)) - throw new TypeError('Invalid strategy: ' + opts.strategy); + if (opts.strategy !== undefined && isInvalidStrategy(opts.strategy)) + throw new TypeError('Invalid strategy: ' + opts.strategy); - if (opts.dictionary !== undefined) { - if (!ArrayBuffer.isView(opts.dictionary)) { - throw new TypeError( - 'Invalid dictionary: it should be a Buffer, TypedArray, or DataView'); - } + if (opts.dictionary !== undefined) { + if (!ArrayBuffer.isView(opts.dictionary)) { + throw new TypeError( + 'Invalid dictionary: it should be a Buffer, TypedArray, or DataView'); } + } - this._handle = new binding.Zlib(mode); - this._handle.onerror = zlibOnError.bind(this); - this._hadError = false; + this._handle = new binding.Zlib(mode); + this._handle.onerror = zlibOnError.bind(this); + this._hadError = false; - var level = constants.Z_DEFAULT_COMPRESSION; - if (Number.isFinite(opts.level)) { - level = opts.level; - } + var level = constants.Z_DEFAULT_COMPRESSION; + if (Number.isFinite(opts.level)) { + level = opts.level; + } - var strategy = constants.Z_DEFAULT_STRATEGY; - if (Number.isFinite(opts.strategy)) { - strategy = opts.strategy; - } + var strategy = constants.Z_DEFAULT_STRATEGY; + if (Number.isFinite(opts.strategy)) { + strategy = opts.strategy; + } - var windowBits = constants.Z_DEFAULT_WINDOWBITS; - if (Number.isFinite(opts.windowBits)) { - windowBits = opts.windowBits; - } + var windowBits = constants.Z_DEFAULT_WINDOWBITS; + if (Number.isFinite(opts.windowBits)) { + windowBits = opts.windowBits; + } - var memLevel = constants.Z_DEFAULT_MEMLEVEL; - if (Number.isFinite(opts.memLevel)) { - memLevel = opts.memLevel; - } + var memLevel = constants.Z_DEFAULT_MEMLEVEL; + if (Number.isFinite(opts.memLevel)) { + memLevel = opts.memLevel; + } - this._handle.init(windowBits, - level, - memLevel, - strategy, - opts.dictionary); + this._handle.init(windowBits, + level, + memLevel, + strategy, + opts.dictionary); - this._buffer = Buffer.allocUnsafe(this._chunkSize); - this._offset = 0; - this._level = level; - this._strategy = strategy; + this._buffer = Buffer.allocUnsafe(this._chunkSize); + this._offset = 0; + this._level = level; + this._strategy = strategy; - this.once('end', this.close); - } + this.once('end', this.close); +} +inherits(Zlib, Transform); - get _closed() { +Object.defineProperty(Zlib.prototype, '_closed', { + configurable: true, + enumerable: true, + get() { return !this._handle; } +}); - params(level, strategy, callback) { - if (level < constants.Z_MIN_LEVEL || - level > constants.Z_MAX_LEVEL) { - throw new RangeError('Invalid compression level: ' + level); - } - if (isInvalidStrategy(strategy)) - throw new TypeError('Invalid strategy: ' + strategy); - - if (this._level !== level || this._strategy !== strategy) { - this.flush(constants.Z_SYNC_FLUSH, - flushCallback.bind(this, level, strategy, callback)); - } else { - process.nextTick(callback); - } +Zlib.prototype.params = function params(level, strategy, callback) { + if (level < constants.Z_MIN_LEVEL || + level > constants.Z_MAX_LEVEL) { + throw new RangeError('Invalid compression level: ' + level); } + if (isInvalidStrategy(strategy)) + throw new TypeError('Invalid strategy: ' + strategy); - reset() { - assert(this._handle, 'zlib binding closed'); - return this._handle.reset(); + if (this._level !== level || this._strategy !== strategy) { + this.flush(constants.Z_SYNC_FLUSH, + flushCallback.bind(this, level, strategy, callback)); + } else { + process.nextTick(callback); } +}; - // This is the _flush function called by the transform class, - // internally, when the last chunk has been written. - _flush(callback) { - this._transform(Buffer.alloc(0), '', callback); - } +Zlib.prototype.reset = function reset() { + assert(this._handle, 'zlib binding closed'); + return this._handle.reset(); +}; - flush(kind, callback) { - var ws = this._writableState; +// This is the _flush function called by the transform class, +// internally, when the last chunk has been written. +Zlib.prototype._flush = function _flush(callback) { + this._transform(Buffer.alloc(0), '', callback); +}; - if (typeof kind === 'function' || (kind === undefined && !callback)) { - callback = kind; - kind = constants.Z_FULL_FLUSH; - } +Zlib.prototype.flush = function flush(kind, callback) { + var ws = this._writableState; - if (ws.ended) { - if (callback) - process.nextTick(callback); - } else if (ws.ending) { - if (callback) - this.once('end', callback); - } else if (ws.needDrain) { - if (callback) { - const drainHandler = () => this.flush(kind, callback); - this.once('drain', drainHandler); - } - } else { - this._flushFlag = kind; - this.write(Buffer.alloc(0), '', callback); - } + if (typeof kind === 'function' || (kind === undefined && !callback)) { + callback = kind; + kind = constants.Z_FULL_FLUSH; } - close(callback) { - _close(this, callback); - process.nextTick(emitCloseNT, this); + if (ws.ended) { + if (callback) + process.nextTick(callback); + } else if (ws.ending) { + if (callback) + this.once('end', callback); + } else if (ws.needDrain) { + if (callback) { + const drainHandler = () => this.flush(kind, callback); + this.once('drain', drainHandler); + } + } else { + this._flushFlag = kind; + this.write(Buffer.alloc(0), '', callback); } +}; - _transform(chunk, encoding, cb) { - var flushFlag; - var ws = this._writableState; - var ending = ws.ending || ws.ended; - var last = ending && (!chunk || ws.length === chunk.byteLength); - - if (chunk !== null && !ArrayBuffer.isView(chunk)) - return cb(new TypeError('invalid input')); - - if (!this._handle) - return cb(new Error('zlib binding closed')); - - // If it's the last chunk, or a final flush, we use the Z_FINISH flush flag - // (or whatever flag was provided using opts.finishFlush). - // If it's explicitly flushing at some other time, then we use - // Z_FULL_FLUSH. Otherwise, use Z_NO_FLUSH for maximum compression - // goodness. - if (last) - flushFlag = this._finishFlushFlag; - else { - flushFlag = this._flushFlag; - // once we've flushed the last of the queue, stop flushing and - // go back to the normal behavior. - if (chunk.byteLength >= ws.length) { - this._flushFlag = this._opts.flush || constants.Z_NO_FLUSH; - } - } +Zlib.prototype.close = function close(callback) { + _close(this, callback); + process.nextTick(emitCloseNT, this); +}; - this._processChunk(chunk, flushFlag, cb); +Zlib.prototype._transform = function _transform(chunk, encoding, cb) { + var flushFlag; + var ws = this._writableState; + var ending = ws.ending || ws.ended; + var last = ending && (!chunk || ws.length === chunk.byteLength); + + if (chunk !== null && !ArrayBuffer.isView(chunk)) + return cb(new TypeError('invalid input')); + + if (!this._handle) + return cb(new Error('zlib binding closed')); + + // If it's the last chunk, or a final flush, we use the Z_FINISH flush flag + // (or whatever flag was provided using opts.finishFlush). + // If it's explicitly flushing at some other time, then we use + // Z_FULL_FLUSH. Otherwise, use Z_NO_FLUSH for maximum compression + // goodness. + if (last) + flushFlag = this._finishFlushFlag; + else { + flushFlag = this._flushFlag; + // once we've flushed the last of the queue, stop flushing and + // go back to the normal behavior. + if (chunk.byteLength >= ws.length) { + this._flushFlag = this._opts.flush || constants.Z_NO_FLUSH; + } } - _processChunk(chunk, flushFlag, cb) { - var availInBefore = chunk && chunk.byteLength; - var availOutBefore = this._chunkSize - this._offset; - var inOff = 0; - - var self = this; - - var async = typeof cb === 'function'; - - if (!async) { - var buffers = []; - var nread = 0; - - var error; - this.on('error', function(er) { - error = er; - }); - - assert(this._handle, 'zlib binding closed'); - do { - var res = this._handle.writeSync(flushFlag, - chunk, // in - inOff, // in_off - availInBefore, // in_len - this._buffer, // out - this._offset, //out_off - availOutBefore); // out_len - } while (!this._hadError && callback(res[0], res[1])); - - if (this._hadError) { - throw error; - } + this._processChunk(chunk, flushFlag, cb); +}; - if (nread >= kMaxLength) { - _close(this); - throw new RangeError(kRangeErrorMessage); - } +Zlib.prototype._processChunk = function _processChunk(chunk, flushFlag, cb) { + var availInBefore = chunk && chunk.byteLength; + var availOutBefore = this._chunkSize - this._offset; + var inOff = 0; - var buf = Buffer.concat(buffers, nread); - _close(this); + var self = this; - return buf; - } + var async = typeof cb === 'function'; + + if (!async) { + var buffers = []; + var nread = 0; + + var error; + this.on('error', function(er) { + error = er; + }); assert(this._handle, 'zlib binding closed'); - var req = this._handle.write(flushFlag, - chunk, // in - inOff, // in_off - availInBefore, // in_len - this._buffer, // out - this._offset, //out_off - availOutBefore); // out_len - - req.buffer = chunk; - req.callback = callback; - - function callback(availInAfter, availOutAfter) { - // When the callback is used in an async write, the callback's - // context is the `req` object that was created. The req object - // is === this._handle, and that's why it's important to null - // out the values after they are done being used. `this._handle` - // can stay in memory longer than the callback and buffer are needed. - if (this) { - this.buffer = null; - this.callback = null; - } + do { + var res = this._handle.writeSync(flushFlag, + chunk, // in + inOff, // in_off + availInBefore, // in_len + this._buffer, // out + this._offset, //out_off + availOutBefore); // out_len + } while (!this._hadError && callback(res[0], res[1])); + + if (this._hadError) { + throw error; + } - if (self._hadError) - return; + if (nread >= kMaxLength) { + _close(this); + throw new RangeError(kRangeErrorMessage); + } - var have = availOutBefore - availOutAfter; - assert(have >= 0, 'have should not go down'); + var buf = Buffer.concat(buffers, nread); + _close(this); - self.bytesRead += availInBefore - availInAfter; + return buf; + } - if (have > 0) { - var out = self._buffer.slice(self._offset, self._offset + have); - self._offset += have; - // serve some output to the consumer. - if (async) { - self.push(out); - } else { - buffers.push(out); - nread += out.byteLength; - } - } + assert(this._handle, 'zlib binding closed'); + var req = this._handle.write(flushFlag, + chunk, // in + inOff, // in_off + availInBefore, // in_len + this._buffer, // out + this._offset, //out_off + availOutBefore); // out_len + + req.buffer = chunk; + req.callback = callback; + + function callback(availInAfter, availOutAfter) { + // When the callback is used in an async write, the callback's + // context is the `req` object that was created. The req object + // is === this._handle, and that's why it's important to null + // out the values after they are done being used. `this._handle` + // can stay in memory longer than the callback and buffer are needed. + if (this) { + this.buffer = null; + this.callback = null; + } - // exhausted the output buffer, or used all the input create a new one. - if (availOutAfter === 0 || self._offset >= self._chunkSize) { - availOutBefore = self._chunkSize; - self._offset = 0; - self._buffer = Buffer.allocUnsafe(self._chunkSize); - } + if (self._hadError) + return; - if (availOutAfter === 0) { - // Not actually done. Need to reprocess. - // Also, update the availInBefore to the availInAfter value, - // so that if we have to hit it a third (fourth, etc.) time, - // it'll have the correct byte counts. - inOff += (availInBefore - availInAfter); - availInBefore = availInAfter; - - if (!async) - return true; - - var newReq = self._handle.write(flushFlag, - chunk, - inOff, - availInBefore, - self._buffer, - self._offset, - self._chunkSize); - newReq.callback = callback; // this same function - newReq.buffer = chunk; - return; + var have = availOutBefore - availOutAfter; + assert(have >= 0, 'have should not go down'); + + self.bytesRead += availInBefore - availInAfter; + + if (have > 0) { + var out = self._buffer.slice(self._offset, self._offset + have); + self._offset += have; + // serve some output to the consumer. + if (async) { + self.push(out); + } else { + buffers.push(out); + nread += out.byteLength; } + } - if (!async) - return false; + // exhausted the output buffer, or used all the input create a new one. + if (availOutAfter === 0 || self._offset >= self._chunkSize) { + availOutBefore = self._chunkSize; + self._offset = 0; + self._buffer = Buffer.allocUnsafe(self._chunkSize); + } + + if (availOutAfter === 0) { + // Not actually done. Need to reprocess. + // Also, update the availInBefore to the availInAfter value, + // so that if we have to hit it a third (fourth, etc.) time, + // it'll have the correct byte counts. + inOff += (availInBefore - availInAfter); + availInBefore = availInAfter; - // finished with the chunk. - cb(); + if (!async) + return true; + + var newReq = self._handle.write(flushFlag, + chunk, + inOff, + availInBefore, + self._buffer, + self._offset, + self._chunkSize); + newReq.callback = callback; // this same function + newReq.buffer = chunk; + return; } + + if (!async) + return false; + + // finished with the chunk. + cb(); } -} +}; function _close(engine, callback) { if (callback) @@ -502,47 +504,54 @@ function emitCloseNT(self) { // generic zlib // minimal 2-byte header -class Deflate extends Zlib { - constructor(opts) { - super(opts, constants.DEFLATE); - } +function Deflate(opts) { + if (!(this instanceof Deflate)) + return new Deflate(opts); + Zlib.call(this, opts, constants.DEFLATE); } +inherits(Deflate, Zlib); -class Inflate extends Zlib { - constructor(opts) { - super(opts, constants.INFLATE); - } +function Inflate(opts) { + if (!(this instanceof Inflate)) + return new Inflate(opts); + Zlib.call(this, opts, constants.INFLATE); } +inherits(Inflate, Zlib); -class Gzip extends Zlib { - constructor(opts) { - super(opts, constants.GZIP); - } +function Gzip(opts) { + if (!(this instanceof Gzip)) + return new Gzip(opts); + Zlib.call(this, opts, constants.GZIP); } +inherits(Gzip, Zlib); -class Gunzip extends Zlib { - constructor(opts) { - super(opts, constants.GUNZIP); - } +function Gunzip(opts) { + if (!(this instanceof Gunzip)) + return new Gunzip(opts); + Zlib.call(this, opts, constants.GUNZIP); } +inherits(Gunzip, Zlib); -class DeflateRaw extends Zlib { - constructor(opts) { - super(opts, constants.DEFLATERAW); - } +function DeflateRaw(opts) { + if (!(this instanceof DeflateRaw)) + return new DeflateRaw(opts); + Zlib.call(this, opts, constants.DEFLATERAW); } +inherits(DeflateRaw, Zlib); -class InflateRaw extends Zlib { - constructor(opts) { - super(opts, constants.INFLATERAW); - } +function InflateRaw(opts) { + if (!(this instanceof InflateRaw)) + return new InflateRaw(opts); + Zlib.call(this, opts, constants.INFLATERAW); } +inherits(InflateRaw, Zlib); -class Unzip extends Zlib { - constructor(opts) { - super(opts, constants.UNZIP); - } +function Unzip(opts) { + if (!(this instanceof Unzip)) + return new Unzip(opts); + Zlib.call(this, opts, constants.UNZIP); } +inherits(Unzip, Zlib); function createConvenienceMethod(type, sync) { if (sync) { @@ -569,13 +578,13 @@ function createProperty(type) { } module.exports = { - Deflate: createClassWrapper(Deflate), - Inflate: createClassWrapper(Inflate), - Gzip: createClassWrapper(Gzip), - Gunzip: createClassWrapper(Gunzip), - DeflateRaw: createClassWrapper(DeflateRaw), - InflateRaw: createClassWrapper(InflateRaw), - Unzip: createClassWrapper(Unzip), + Deflate, + Inflate, + Gzip, + Gunzip, + DeflateRaw, + InflateRaw, + Unzip, // Convenience methods. // compress/decompress a string or buffer in one step. diff --git a/test/parallel/test-zlib-deflate-raw-inherits.js b/test/parallel/test-zlib-deflate-raw-inherits.js new file mode 100644 index 00000000000000..a24726a3fbe465 --- /dev/null +++ b/test/parallel/test-zlib-deflate-raw-inherits.js @@ -0,0 +1,27 @@ +'use strict'; + +require('../common'); +const { DeflateRaw } = require('zlib'); +const { inherits } = require('util'); +const { Readable } = require('stream'); + +// validates that zlib.DeflateRaw can be inherited +// with util.inherits + +function NotInitialized(options) { + DeflateRaw.call(this, options); + this.prop = true; +} +inherits(NotInitialized, DeflateRaw); + +const dest = new NotInitialized(); + +const read = new Readable({ + read() { + this.push(Buffer.from('a test string')); + this.push(null); + } +}); + +read.pipe(dest); +dest.resume();