| 'use strict' |
| |
| const assert = require('assert') |
| const Buffer = require('buffer').Buffer |
| const realZlib = require('zlib') |
| |
| const constants = exports.constants = require('./constants.js') |
| const Minipass = require('minipass') |
| |
| const OriginalBufferConcat = Buffer.concat |
| |
| const _superWrite = Symbol('_superWrite') |
| class ZlibError extends Error { |
| constructor (err) { |
| super('zlib: ' + err.message) |
| this.code = err.code |
| this.errno = err.errno |
| /* istanbul ignore if */ |
| if (!this.code) |
| this.code = 'ZLIB_ERROR' |
| |
| this.message = 'zlib: ' + err.message |
| Error.captureStackTrace(this, this.constructor) |
| } |
| |
| get name () { |
| return 'ZlibError' |
| } |
| } |
| |
| // the Zlib class they all inherit from |
| // 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. |
| const _opts = Symbol('opts') |
| const _flushFlag = Symbol('flushFlag') |
| const _finishFlushFlag = Symbol('finishFlushFlag') |
| const _fullFlushFlag = Symbol('fullFlushFlag') |
| const _handle = Symbol('handle') |
| const _onError = Symbol('onError') |
| const _sawError = Symbol('sawError') |
| const _level = Symbol('level') |
| const _strategy = Symbol('strategy') |
| const _ended = Symbol('ended') |
| const _defaultFullFlush = Symbol('_defaultFullFlush') |
| |
| class ZlibBase extends Minipass { |
| constructor (opts, mode) { |
| if (!opts || typeof opts !== 'object') |
| throw new TypeError('invalid options for ZlibBase constructor') |
| |
| super(opts) |
| this[_sawError] = false |
| this[_ended] = false |
| this[_opts] = opts |
| |
| this[_flushFlag] = opts.flush |
| this[_finishFlushFlag] = opts.finishFlush |
| // this will throw if any options are invalid for the class selected |
| try { |
| this[_handle] = new realZlib[mode](opts) |
| } catch (er) { |
| // make sure that all errors get decorated properly |
| throw new ZlibError(er) |
| } |
| |
| this[_onError] = (err) => { |
| // no sense raising multiple errors, since we abort on the first one. |
| if (this[_sawError]) |
| return |
| |
| this[_sawError] = true |
| |
| // there is no way to cleanly recover. |
| // continuing only obscures problems. |
| this.close() |
| this.emit('error', err) |
| } |
| |
| this[_handle].on('error', er => this[_onError](new ZlibError(er))) |
| this.once('end', () => this.close) |
| } |
| |
| close () { |
| if (this[_handle]) { |
| this[_handle].close() |
| this[_handle] = null |
| this.emit('close') |
| } |
| } |
| |
| reset () { |
| if (!this[_sawError]) { |
| assert(this[_handle], 'zlib binding closed') |
| return this[_handle].reset() |
| } |
| } |
| |
| flush (flushFlag) { |
| if (this.ended) |
| return |
| |
| if (typeof flushFlag !== 'number') |
| flushFlag = this[_fullFlushFlag] |
| this.write(Object.assign(Buffer.alloc(0), { [_flushFlag]: flushFlag })) |
| } |
| |
| end (chunk, encoding, cb) { |
| if (chunk) |
| this.write(chunk, encoding) |
| this.flush(this[_finishFlushFlag]) |
| this[_ended] = true |
| return super.end(null, null, cb) |
| } |
| |
| get ended () { |
| return this[_ended] |
| } |
| |
| write (chunk, encoding, cb) { |
| // process the chunk using the sync process |
| // then super.write() all the outputted chunks |
| if (typeof encoding === 'function') |
| cb = encoding, encoding = 'utf8' |
| |
| if (typeof chunk === 'string') |
| chunk = Buffer.from(chunk, encoding) |
| |
| if (this[_sawError]) |
| return |
| assert(this[_handle], 'zlib binding closed') |
| |
| // _processChunk tries to .close() the native handle after it's done, so we |
| // intercept that by temporarily making it a no-op. |
| const nativeHandle = this[_handle]._handle |
| const originalNativeClose = nativeHandle.close |
| nativeHandle.close = () => {} |
| const originalClose = this[_handle].close |
| this[_handle].close = () => {} |
| // It also calls `Buffer.concat()` at the end, which may be convenient |
| // for some, but which we are not interested in as it slows us down. |
| Buffer.concat = (args) => args |
| let result |
| try { |
| const flushFlag = typeof chunk[_flushFlag] === 'number' |
| ? chunk[_flushFlag] : this[_flushFlag] |
| result = this[_handle]._processChunk(chunk, flushFlag) |
| // if we don't throw, reset it back how it was |
| Buffer.concat = OriginalBufferConcat |
| } catch (err) { |
| // or if we do, put Buffer.concat() back before we emit error |
| // Error events call into user code, which may call Buffer.concat() |
| Buffer.concat = OriginalBufferConcat |
| this[_onError](new ZlibError(err)) |
| } finally { |
| if (this[_handle]) { |
| // Core zlib resets `_handle` to null after attempting to close the |
| // native handle. Our no-op handler prevented actual closure, but we |
| // need to restore the `._handle` property. |
| this[_handle]._handle = nativeHandle |
| nativeHandle.close = originalNativeClose |
| this[_handle].close = originalClose |
| // `_processChunk()` adds an 'error' listener. If we don't remove it |
| // after each call, these handlers start piling up. |
| this[_handle].removeAllListeners('error') |
| // make sure OUR error listener is still attached tho |
| } |
| } |
| |
| if (this[_handle]) |
| this[_handle].on('error', er => this[_onError](new ZlibError(er))) |
| |
| let writeReturn |
| if (result) { |
| if (Array.isArray(result) && result.length > 0) { |
| // The first buffer is always `handle._outBuffer`, which would be |
| // re-used for later invocations; so, we always have to copy that one. |
| writeReturn = this[_superWrite](Buffer.from(result[0])) |
| for (let i = 1; i < result.length; i++) { |
| writeReturn = this[_superWrite](result[i]) |
| } |
| } else { |
| writeReturn = this[_superWrite](Buffer.from(result)) |
| } |
| } |
| |
| if (cb) |
| cb() |
| return writeReturn |
| } |
| |
| [_superWrite] (data) { |
| return super.write(data) |
| } |
| } |
| |
| class Zlib extends ZlibBase { |
| constructor (opts, mode) { |
| opts = opts || {} |
| |
| opts.flush = opts.flush || constants.Z_NO_FLUSH |
| opts.finishFlush = opts.finishFlush || constants.Z_FINISH |
| super(opts, mode) |
| |
| this[_fullFlushFlag] = constants.Z_FULL_FLUSH |
| this[_level] = opts.level |
| this[_strategy] = opts.strategy |
| } |
| |
| params (level, strategy) { |
| if (this[_sawError]) |
| return |
| |
| if (!this[_handle]) |
| throw new Error('cannot switch params when binding is closed') |
| |
| // no way to test this without also not supporting params at all |
| /* istanbul ignore if */ |
| if (!this[_handle].params) |
| throw new Error('not supported in this implementation') |
| |
| if (this[_level] !== level || this[_strategy] !== strategy) { |
| this.flush(constants.Z_SYNC_FLUSH) |
| assert(this[_handle], 'zlib binding closed') |
| // .params() calls .flush(), but the latter is always async in the |
| // core zlib. We override .flush() temporarily to intercept that and |
| // flush synchronously. |
| const origFlush = this[_handle].flush |
| this[_handle].flush = (flushFlag, cb) => { |
| this.flush(flushFlag) |
| cb() |
| } |
| try { |
| this[_handle].params(level, strategy) |
| } finally { |
| this[_handle].flush = origFlush |
| } |
| /* istanbul ignore else */ |
| if (this[_handle]) { |
| this[_level] = level |
| this[_strategy] = strategy |
| } |
| } |
| } |
| } |
| |
| // minimal 2-byte header |
| class Deflate extends Zlib { |
| constructor (opts) { |
| super(opts, 'Deflate') |
| } |
| } |
| |
| class Inflate extends Zlib { |
| constructor (opts) { |
| super(opts, 'Inflate') |
| } |
| } |
| |
| // gzip - bigger header, same deflate compression |
| const _portable = Symbol('_portable') |
| class Gzip extends Zlib { |
| constructor (opts) { |
| super(opts, 'Gzip') |
| this[_portable] = opts && !!opts.portable |
| } |
| |
| [_superWrite] (data) { |
| if (!this[_portable]) |
| return super[_superWrite](data) |
| |
| // we'll always get the header emitted in one first chunk |
| // overwrite the OS indicator byte with 0xFF |
| this[_portable] = false |
| data[9] = 255 |
| return super[_superWrite](data) |
| } |
| } |
| |
| class Gunzip extends Zlib { |
| constructor (opts) { |
| super(opts, 'Gunzip') |
| } |
| } |
| |
| // raw - no header |
| class DeflateRaw extends Zlib { |
| constructor (opts) { |
| super(opts, 'DeflateRaw') |
| } |
| } |
| |
| class InflateRaw extends Zlib { |
| constructor (opts) { |
| super(opts, 'InflateRaw') |
| } |
| } |
| |
| // auto-detect header. |
| class Unzip extends Zlib { |
| constructor (opts) { |
| super(opts, 'Unzip') |
| } |
| } |
| |
| class Brotli extends ZlibBase { |
| constructor (opts, mode) { |
| opts = opts || {} |
| |
| opts.flush = opts.flush || constants.BROTLI_OPERATION_PROCESS |
| opts.finishFlush = opts.finishFlush || constants.BROTLI_OPERATION_FINISH |
| |
| super(opts, mode) |
| |
| this[_fullFlushFlag] = constants.BROTLI_OPERATION_FLUSH |
| } |
| } |
| |
| class BrotliCompress extends Brotli { |
| constructor (opts) { |
| super(opts, 'BrotliCompress') |
| } |
| } |
| |
| class BrotliDecompress extends Brotli { |
| constructor (opts) { |
| super(opts, 'BrotliDecompress') |
| } |
| } |
| |
| exports.Deflate = Deflate |
| exports.Inflate = Inflate |
| exports.Gzip = Gzip |
| exports.Gunzip = Gunzip |
| exports.DeflateRaw = DeflateRaw |
| exports.InflateRaw = InflateRaw |
| exports.Unzip = Unzip |
| /* istanbul ignore else */ |
| if (typeof realZlib.BrotliCompress === 'function') { |
| exports.BrotliCompress = BrotliCompress |
| exports.BrotliDecompress = BrotliDecompress |
| } else { |
| exports.BrotliCompress = exports.BrotliDecompress = class { |
| constructor () { |
| throw new Error('Brotli is not supported in this version of Node.js') |
| } |
| } |
| } |