'use strict' const assert = require('assert') const Buffer = require('buffer').Buffer const binding = process.binding('zlib') const constants = exports.constants = require('./constants.js') const MiniPass = require('minipass') // translation table for return codes. const codes = new Map([ [constants.Z_OK, 'Z_OK'], [constants.Z_STREAM_END, 'Z_STREAM_END'], [constants.Z_NEED_DICT, 'Z_NEED_DICT'], [constants.Z_ERRNO, 'Z_ERRNO'], [constants.Z_STREAM_ERROR, 'Z_STREAM_ERROR'], [constants.Z_DATA_ERROR, 'Z_DATA_ERROR'], [constants.Z_MEM_ERROR, 'Z_MEM_ERROR'], [constants.Z_BUF_ERROR, 'Z_BUF_ERROR'], [constants.Z_VERSION_ERROR, 'Z_VERSION_ERROR'] ]) const validFlushFlags = new Set([ constants.Z_NO_FLUSH, constants.Z_PARTIAL_FLUSH, constants.Z_SYNC_FLUSH, constants.Z_FULL_FLUSH, constants.Z_FINISH, constants.Z_BLOCK ]) const strategies = new Set([ constants.Z_FILTERED, constants.Z_HUFFMAN_ONLY, constants.Z_RLE, constants.Z_FIXED, constants.Z_DEFAULT_STRATEGY ]) // 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 _chunkSize = Symbol('chunkSize') const _flushFlag = Symbol('flushFlag') const _finishFlush = Symbol('finishFlush') const _handle = Symbol('handle') const _hadError = Symbol('hadError') const _buffer = Symbol('buffer') const _offset = Symbol('offset') const _level = Symbol('level') const _strategy = Symbol('strategy') const _ended = Symbol('ended') const _writeState = Symbol('writeState') class Zlib extends MiniPass { constructor (opts, mode) { super(opts) this[_ended] = false this[_opts] = opts = opts || {} this[_chunkSize] = opts.chunkSize || constants.Z_DEFAULT_CHUNK if (opts.flush && !validFlushFlags.has(opts.flush)) { throw new Error('Invalid flush flag: ' + opts.flush) } if (opts.finishFlush && !validFlushFlags.has(opts.finishFlush)) { throw new Error('Invalid flush flag: ' + opts.finishFlush) } this[_flushFlag] = opts.flush || constants.Z_NO_FLUSH this[_finishFlush] = typeof opts.finishFlush !== 'undefined' ? opts.finishFlush : constants.Z_FINISH if (opts.chunkSize) { if (opts.chunkSize < constants.Z_MIN_CHUNK) { throw new Error('Invalid chunk size: ' + opts.chunkSize) } } if (opts.windowBits) { if (opts.windowBits < constants.Z_MIN_WINDOWBITS || opts.windowBits > constants.Z_MAX_WINDOWBITS) { throw new Error('Invalid windowBits: ' + opts.windowBits) } } if (opts.level) { if (opts.level < constants.Z_MIN_LEVEL || opts.level > constants.Z_MAX_LEVEL) { throw new Error('Invalid compression level: ' + opts.level) } } if (opts.memLevel) { if (opts.memLevel < constants.Z_MIN_MEMLEVEL || opts.memLevel > constants.Z_MAX_MEMLEVEL) { throw new Error('Invalid memLevel: ' + opts.memLevel) } } if (opts.strategy && !(strategies.has(opts.strategy))) throw new Error('Invalid strategy: ' + opts.strategy) if (opts.dictionary) { if (!(opts.dictionary instanceof Buffer)) { throw new Error('Invalid dictionary: it should be a Buffer instance') } } this[_handle] = new binding.Zlib(mode) this[_hadError] = false this[_handle].onerror = (message, errno) => { // there is no way to cleanly recover. // continuing only obscures problems. this.close() this[_hadError] = true const error = new Error(message) error.errno = errno error.code = codes.get(errno) this.emit('error', error) } const level = typeof opts.level === 'number' ? opts.level : constants.Z_DEFAULT_COMPRESSION var strategy = typeof opts.strategy === 'number' ? opts.strategy : constants.Z_DEFAULT_STRATEGY this[_writeState] = new Uint32Array(2); const window = opts.windowBits || constants.Z_DEFAULT_WINDOWBITS const memLevel = opts.memLevel || constants.Z_DEFAULT_MEMLEVEL // API changed in node v9 /* istanbul ignore next */ if (/^v[0-8]\./.test(process.version)) { this[_handle].init(window, level, memLevel, strategy, opts.dictionary) } else { this[_handle].init(window, level, memLevel, strategy, this[_writeState], () => {}, opts.dictionary) } this[_buffer] = Buffer.allocUnsafe(this[_chunkSize]) this[_offset] = 0 this[_level] = level this[_strategy] = strategy this.once('end', this.close) } close () { if (this[_handle]) { this[_handle].close() this[_handle] = null this.emit('close') } } params (level, strategy) { 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 (level < constants.Z_MIN_LEVEL || level > constants.Z_MAX_LEVEL) { throw new RangeError('Invalid compression level: ' + level) } if (!(strategies.has(strategy))) throw new TypeError('Invalid strategy: ' + strategy) if (this[_level] !== level || this[_strategy] !== strategy) { this.flush(constants.Z_SYNC_FLUSH) assert(this[_handle], 'zlib binding closed') this[_handle].params(level, strategy) /* istanbul ignore else */ if (!this[_hadError]) { this[_level] = level this[_strategy] = strategy } } } reset () { assert(this[_handle], 'zlib binding closed') return this[_handle].reset() } flush (kind) { if (kind === undefined) kind = constants.Z_FULL_FLUSH if (this.ended) return const flushFlag = this[_flushFlag] this[_flushFlag] = kind this.write(Buffer.alloc(0)) this[_flushFlag] = flushFlag } end (chunk, encoding, cb) { if (chunk) this.write(chunk, encoding) this.flush(this[_finishFlush]) 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 = new Buffer(chunk, encoding) let availInBefore = chunk && chunk.length let availOutBefore = this[_chunkSize] - this[_offset] let inOff = 0 // the offset of the input buffer const flushFlag = this[_flushFlag] let writeReturn = true assert(this[_handle], 'zlib binding closed') do { let res = this[_handle].writeSync( flushFlag, chunk, // in inOff, // in_off availInBefore, // in_len this[_buffer], // out this[_offset], //out_off availOutBefore // out_len ) if (this[_hadError]) break // API changed in v9 /* istanbul ignore next */ let availInAfter = res ? res[0] : this[_writeState][1] /* istanbul ignore next */ let availOutAfter = res ? res[1] : this[_writeState][0] const have = availOutBefore - availOutAfter assert(have >= 0, 'have should not go down') if (have > 0) { const out = this[_buffer].slice( this[_offset], this[_offset] + have ) this[_offset] += have // serve some output to the consumer. writeReturn = super.write(out) && writeReturn } // exhausted the output buffer, or used all the input create a new one. if (availOutAfter === 0 || this[_offset] >= this[_chunkSize]) { availOutBefore = this[_chunkSize] this[_offset] = 0 this[_buffer] = Buffer.allocUnsafe(this[_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 continue } break } while (!this[_hadError]) if (cb) cb() return writeReturn } } // minimal 2-byte header class Deflate extends Zlib { constructor (opts) { super(opts, constants.DEFLATE) } } class Inflate extends Zlib { constructor (opts) { super(opts, constants.INFLATE) } } // gzip - bigger header, same deflate compression class Gzip extends Zlib { constructor (opts) { super(opts, constants.GZIP) } } class Gunzip extends Zlib { constructor (opts) { super(opts, constants.GUNZIP) } } // raw - no header class DeflateRaw extends Zlib { constructor (opts) { super(opts, constants.DEFLATERAW) } } class InflateRaw extends Zlib { constructor (opts) { super(opts, constants.INFLATERAW) } } // auto-detect header. class Unzip extends Zlib { constructor (opts) { super(opts, constants.UNZIP) } } exports.Deflate = Deflate exports.Inflate = Inflate exports.Gzip = Gzip exports.Gunzip = Gunzip exports.DeflateRaw = DeflateRaw exports.InflateRaw = InflateRaw exports.Unzip = Unzip