1'use strict' 2 3const assert = require('assert') 4const Buffer = require('buffer').Buffer 5const realZlib = require('zlib') 6 7const constants = exports.constants = require('./constants.js') 8const Minipass = require('minipass') 9 10const OriginalBufferConcat = Buffer.concat 11 12class ZlibError extends Error { 13 constructor (err) { 14 super('zlib: ' + err.message) 15 this.code = err.code 16 this.errno = err.errno 17 /* istanbul ignore if */ 18 if (!this.code) 19 this.code = 'ZLIB_ERROR' 20 21 this.message = 'zlib: ' + err.message 22 Error.captureStackTrace(this, this.constructor) 23 } 24 25 get name () { 26 return 'ZlibError' 27 } 28} 29 30// the Zlib class they all inherit from 31// This thing manages the queue of requests, and returns 32// true or false if there is anything in the queue when 33// you call the .write() method. 34const _opts = Symbol('opts') 35const _flushFlag = Symbol('flushFlag') 36const _finishFlushFlag = Symbol('finishFlushFlag') 37const _fullFlushFlag = Symbol('fullFlushFlag') 38const _handle = Symbol('handle') 39const _onError = Symbol('onError') 40const _sawError = Symbol('sawError') 41const _level = Symbol('level') 42const _strategy = Symbol('strategy') 43const _ended = Symbol('ended') 44const _defaultFullFlush = Symbol('_defaultFullFlush') 45 46class ZlibBase extends Minipass { 47 constructor (opts, mode) { 48 if (!opts || typeof opts !== 'object') 49 throw new TypeError('invalid options for ZlibBase constructor') 50 51 super(opts) 52 this[_ended] = false 53 this[_opts] = opts 54 55 this[_flushFlag] = opts.flush 56 this[_finishFlushFlag] = opts.finishFlush 57 // this will throw if any options are invalid for the class selected 58 try { 59 this[_handle] = new realZlib[mode](opts) 60 } catch (er) { 61 // make sure that all errors get decorated properly 62 throw new ZlibError(er) 63 } 64 65 this[_onError] = (err) => { 66 this[_sawError] = true 67 // there is no way to cleanly recover. 68 // continuing only obscures problems. 69 this.close() 70 this.emit('error', err) 71 } 72 73 this[_handle].on('error', er => this[_onError](new ZlibError(er))) 74 this.once('end', () => this.close) 75 } 76 77 close () { 78 if (this[_handle]) { 79 this[_handle].close() 80 this[_handle] = null 81 this.emit('close') 82 } 83 } 84 85 reset () { 86 if (!this[_sawError]) { 87 assert(this[_handle], 'zlib binding closed') 88 return this[_handle].reset() 89 } 90 } 91 92 flush (flushFlag) { 93 if (this.ended) 94 return 95 96 if (typeof flushFlag !== 'number') 97 flushFlag = this[_fullFlushFlag] 98 this.write(Object.assign(Buffer.alloc(0), { [_flushFlag]: flushFlag })) 99 } 100 101 end (chunk, encoding, cb) { 102 if (chunk) 103 this.write(chunk, encoding) 104 this.flush(this[_finishFlushFlag]) 105 this[_ended] = true 106 return super.end(null, null, cb) 107 } 108 109 get ended () { 110 return this[_ended] 111 } 112 113 write (chunk, encoding, cb) { 114 // process the chunk using the sync process 115 // then super.write() all the outputted chunks 116 if (typeof encoding === 'function') 117 cb = encoding, encoding = 'utf8' 118 119 if (typeof chunk === 'string') 120 chunk = Buffer.from(chunk, encoding) 121 122 if (this[_sawError]) 123 return 124 assert(this[_handle], 'zlib binding closed') 125 126 // _processChunk tries to .close() the native handle after it's done, so we 127 // intercept that by temporarily making it a no-op. 128 const nativeHandle = this[_handle]._handle 129 const originalNativeClose = nativeHandle.close 130 nativeHandle.close = () => {} 131 const originalClose = this[_handle].close 132 this[_handle].close = () => {} 133 // It also calls `Buffer.concat()` at the end, which may be convenient 134 // for some, but which we are not interested in as it slows us down. 135 Buffer.concat = (args) => args 136 let result 137 try { 138 const flushFlag = typeof chunk[_flushFlag] === 'number' 139 ? chunk[_flushFlag] : this[_flushFlag] 140 result = this[_handle]._processChunk(chunk, flushFlag) 141 // if we don't throw, reset it back how it was 142 Buffer.concat = OriginalBufferConcat 143 } catch (err) { 144 // or if we do, put Buffer.concat() back before we emit error 145 // Error events call into user code, which may call Buffer.concat() 146 Buffer.concat = OriginalBufferConcat 147 this[_onError](new ZlibError(err)) 148 } finally { 149 if (this[_handle]) { 150 // Core zlib resets `_handle` to null after attempting to close the 151 // native handle. Our no-op handler prevented actual closure, but we 152 // need to restore the `._handle` property. 153 this[_handle]._handle = nativeHandle 154 nativeHandle.close = originalNativeClose 155 this[_handle].close = originalClose 156 // `_processChunk()` adds an 'error' listener. If we don't remove it 157 // after each call, these handlers start piling up. 158 this[_handle].removeAllListeners('error') 159 } 160 } 161 162 let writeReturn 163 if (result) { 164 if (Array.isArray(result) && result.length > 0) { 165 // The first buffer is always `handle._outBuffer`, which would be 166 // re-used for later invocations; so, we always have to copy that one. 167 writeReturn = super.write(Buffer.from(result[0])) 168 for (let i = 1; i < result.length; i++) { 169 writeReturn = super.write(result[i]) 170 } 171 } else { 172 writeReturn = super.write(Buffer.from(result)) 173 } 174 } 175 176 if (cb) 177 cb() 178 return writeReturn 179 } 180} 181 182class Zlib extends ZlibBase { 183 constructor (opts, mode) { 184 opts = opts || {} 185 186 opts.flush = opts.flush || constants.Z_NO_FLUSH 187 opts.finishFlush = opts.finishFlush || constants.Z_FINISH 188 super(opts, mode) 189 190 this[_fullFlushFlag] = constants.Z_FULL_FLUSH 191 this[_level] = opts.level 192 this[_strategy] = opts.strategy 193 } 194 195 params (level, strategy) { 196 if (this[_sawError]) 197 return 198 199 if (!this[_handle]) 200 throw new Error('cannot switch params when binding is closed') 201 202 // no way to test this without also not supporting params at all 203 /* istanbul ignore if */ 204 if (!this[_handle].params) 205 throw new Error('not supported in this implementation') 206 207 if (this[_level] !== level || this[_strategy] !== strategy) { 208 this.flush(constants.Z_SYNC_FLUSH) 209 assert(this[_handle], 'zlib binding closed') 210 // .params() calls .flush(), but the latter is always async in the 211 // core zlib. We override .flush() temporarily to intercept that and 212 // flush synchronously. 213 const origFlush = this[_handle].flush 214 this[_handle].flush = (flushFlag, cb) => { 215 this.flush(flushFlag) 216 cb() 217 } 218 try { 219 this[_handle].params(level, strategy) 220 } finally { 221 this[_handle].flush = origFlush 222 } 223 /* istanbul ignore else */ 224 if (this[_handle]) { 225 this[_level] = level 226 this[_strategy] = strategy 227 } 228 } 229 } 230} 231 232// minimal 2-byte header 233class Deflate extends Zlib { 234 constructor (opts) { 235 super(opts, 'Deflate') 236 } 237} 238 239class Inflate extends Zlib { 240 constructor (opts) { 241 super(opts, 'Inflate') 242 } 243} 244 245// gzip - bigger header, same deflate compression 246class Gzip extends Zlib { 247 constructor (opts) { 248 super(opts, 'Gzip') 249 } 250} 251 252class Gunzip extends Zlib { 253 constructor (opts) { 254 super(opts, 'Gunzip') 255 } 256} 257 258// raw - no header 259class DeflateRaw extends Zlib { 260 constructor (opts) { 261 super(opts, 'DeflateRaw') 262 } 263} 264 265class InflateRaw extends Zlib { 266 constructor (opts) { 267 super(opts, 'InflateRaw') 268 } 269} 270 271// auto-detect header. 272class Unzip extends Zlib { 273 constructor (opts) { 274 super(opts, 'Unzip') 275 } 276} 277 278class Brotli extends ZlibBase { 279 constructor (opts, mode) { 280 opts = opts || {} 281 282 opts.flush = opts.flush || constants.BROTLI_OPERATION_PROCESS 283 opts.finishFlush = opts.finishFlush || constants.BROTLI_OPERATION_FINISH 284 285 super(opts, mode) 286 287 this[_fullFlushFlag] = constants.BROTLI_OPERATION_FLUSH 288 } 289} 290 291class BrotliCompress extends Brotli { 292 constructor (opts) { 293 super(opts, 'BrotliCompress') 294 } 295} 296 297class BrotliDecompress extends Brotli { 298 constructor (opts) { 299 super(opts, 'BrotliDecompress') 300 } 301} 302 303exports.Deflate = Deflate 304exports.Inflate = Inflate 305exports.Gzip = Gzip 306exports.Gunzip = Gunzip 307exports.DeflateRaw = DeflateRaw 308exports.InflateRaw = InflateRaw 309exports.Unzip = Unzip 310/* istanbul ignore else */ 311if (typeof realZlib.BrotliCompress === 'function') { 312 exports.BrotliCompress = BrotliCompress 313 exports.BrotliDecompress = BrotliDecompress 314} else { 315 exports.BrotliCompress = exports.BrotliDecompress = class { 316 constructor () { 317 throw new Error('Brotli is not supported in this version of Node.js') 318 } 319 } 320} 321