123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- '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')
- }
- }
- }
|