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