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