1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23 24const { 25 ArrayBuffer, 26 Error, 27 MathMax, 28 NumberIsFinite, 29 NumberIsNaN, 30 ObjectDefineProperties, 31 ObjectDefineProperty, 32 ObjectFreeze, 33 ObjectGetPrototypeOf, 34 ObjectKeys, 35 ObjectSetPrototypeOf, 36 Symbol, 37 Uint32Array, 38} = primordials; 39 40const { 41 codes: { 42 ERR_BROTLI_INVALID_PARAM, 43 ERR_BUFFER_TOO_LARGE, 44 ERR_INVALID_ARG_TYPE, 45 ERR_OUT_OF_RANGE, 46 ERR_ZLIB_INITIALIZATION_FAILED, 47 }, 48 hideStackFrames 49} = require('internal/errors'); 50const Transform = require('_stream_transform'); 51const { 52 deprecate 53} = require('internal/util'); 54const { 55 isArrayBufferView, 56 isAnyArrayBuffer 57} = require('internal/util/types'); 58const binding = internalBinding('zlib'); 59const assert = require('internal/assert'); 60const { 61 Buffer, 62 kMaxLength 63} = require('buffer'); 64const { owner_symbol } = require('internal/async_hooks').symbols; 65 66const kFlushFlag = Symbol('kFlushFlag'); 67 68const constants = internalBinding('constants').zlib; 69const { 70 // Zlib flush levels 71 Z_NO_FLUSH, Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH, 72 // Zlib option values 73 Z_MIN_CHUNK, Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_MIN_LEVEL, Z_MAX_LEVEL, 74 Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_CHUNK, Z_DEFAULT_COMPRESSION, 75 Z_DEFAULT_STRATEGY, Z_DEFAULT_WINDOWBITS, Z_DEFAULT_MEMLEVEL, Z_FIXED, 76 // Node's compression stream modes (node_zlib_mode) 77 DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP, 78 BROTLI_DECODE, BROTLI_ENCODE, 79 // Brotli operations (~flush levels) 80 BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_FLUSH, 81 BROTLI_OPERATION_FINISH 82} = constants; 83 84// Translation table for return codes. 85const codes = { 86 Z_OK: constants.Z_OK, 87 Z_STREAM_END: constants.Z_STREAM_END, 88 Z_NEED_DICT: constants.Z_NEED_DICT, 89 Z_ERRNO: constants.Z_ERRNO, 90 Z_STREAM_ERROR: constants.Z_STREAM_ERROR, 91 Z_DATA_ERROR: constants.Z_DATA_ERROR, 92 Z_MEM_ERROR: constants.Z_MEM_ERROR, 93 Z_BUF_ERROR: constants.Z_BUF_ERROR, 94 Z_VERSION_ERROR: constants.Z_VERSION_ERROR 95}; 96 97for (const ckey of ObjectKeys(codes)) { 98 codes[codes[ckey]] = ckey; 99} 100 101function zlibBuffer(engine, buffer, callback) { 102 if (typeof callback !== 'function') 103 throw new ERR_INVALID_ARG_TYPE('callback', 'function', callback); 104 // Streams do not support non-Buffer ArrayBufferViews yet. Convert it to a 105 // Buffer without copying. 106 if (isArrayBufferView(buffer) && 107 ObjectGetPrototypeOf(buffer) !== Buffer.prototype) { 108 buffer = Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength); 109 } else if (isAnyArrayBuffer(buffer)) { 110 buffer = Buffer.from(buffer); 111 } 112 engine.buffers = null; 113 engine.nread = 0; 114 engine.cb = callback; 115 engine.on('data', zlibBufferOnData); 116 engine.on('error', zlibBufferOnError); 117 engine.on('end', zlibBufferOnEnd); 118 engine.end(buffer); 119} 120 121function zlibBufferOnData(chunk) { 122 if (!this.buffers) 123 this.buffers = [chunk]; 124 else 125 this.buffers.push(chunk); 126 this.nread += chunk.length; 127 if (this.nread > this._maxOutputLength) { 128 this.close(); 129 this.removeAllListeners('end'); 130 this.cb(new ERR_BUFFER_TOO_LARGE(this._maxOutputLength)); 131 } 132} 133 134function zlibBufferOnError(err) { 135 this.removeAllListeners('end'); 136 this.cb(err); 137} 138 139function zlibBufferOnEnd() { 140 let buf; 141 if (this.nread === 0) { 142 buf = Buffer.alloc(0); 143 } else { 144 const bufs = this.buffers; 145 buf = (bufs.length === 1 ? bufs[0] : Buffer.concat(bufs, this.nread)); 146 } 147 this.close(); 148 if (this._info) 149 this.cb(null, { buffer: buf, engine: this }); 150 else 151 this.cb(null, buf); 152} 153 154function zlibBufferSync(engine, buffer) { 155 if (typeof buffer === 'string') { 156 buffer = Buffer.from(buffer); 157 } else if (!isArrayBufferView(buffer)) { 158 if (isAnyArrayBuffer(buffer)) { 159 buffer = Buffer.from(buffer); 160 } else { 161 throw new ERR_INVALID_ARG_TYPE( 162 'buffer', 163 ['string', 'Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'], 164 buffer 165 ); 166 } 167 } 168 buffer = processChunkSync(engine, buffer, engine._finishFlushFlag); 169 if (engine._info) 170 return { buffer, engine }; 171 return buffer; 172} 173 174function zlibOnError(message, errno, code) { 175 const self = this[owner_symbol]; 176 // There is no way to cleanly recover. 177 // Continuing only obscures problems. 178 _close(self); 179 self._hadError = true; 180 181 // eslint-disable-next-line no-restricted-syntax 182 const error = new Error(message); 183 error.errno = errno; 184 error.code = code; 185 self.emit('error', error); 186} 187 188// 1. Returns false for undefined and NaN 189// 2. Returns true for finite numbers 190// 3. Throws ERR_INVALID_ARG_TYPE for non-numbers 191// 4. Throws ERR_OUT_OF_RANGE for infinite numbers 192const checkFiniteNumber = hideStackFrames((number, name) => { 193 // Common case 194 if (number === undefined) { 195 return false; 196 } 197 198 if (NumberIsFinite(number)) { 199 return true; // Is a valid number 200 } 201 202 if (NumberIsNaN(number)) { 203 return false; 204 } 205 206 // Other non-numbers 207 if (typeof number !== 'number') { 208 throw new ERR_INVALID_ARG_TYPE(name, 'number', number); 209 } 210 211 // Infinite numbers 212 throw new ERR_OUT_OF_RANGE(name, 'a finite number', number); 213}); 214 215// 1. Returns def for number when it's undefined or NaN 216// 2. Returns number for finite numbers >= lower and <= upper 217// 3. Throws ERR_INVALID_ARG_TYPE for non-numbers 218// 4. Throws ERR_OUT_OF_RANGE for infinite numbers or numbers > upper or < lower 219const checkRangesOrGetDefault = hideStackFrames( 220 (number, name, lower, upper, def) => { 221 if (!checkFiniteNumber(number, name)) { 222 return def; 223 } 224 if (number < lower || number > upper) { 225 throw new ERR_OUT_OF_RANGE(name, 226 `>= ${lower} and <= ${upper}`, number); 227 } 228 return number; 229 } 230); 231 232// The base class for all Zlib-style streams. 233function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) { 234 let chunkSize = Z_DEFAULT_CHUNK; 235 let maxOutputLength = kMaxLength; 236 // The ZlibBase class is not exported to user land, the mode should only be 237 // passed in by us. 238 assert(typeof mode === 'number'); 239 assert(mode >= DEFLATE && mode <= BROTLI_ENCODE); 240 241 if (opts) { 242 chunkSize = opts.chunkSize; 243 if (!checkFiniteNumber(chunkSize, 'options.chunkSize')) { 244 chunkSize = Z_DEFAULT_CHUNK; 245 } else if (chunkSize < Z_MIN_CHUNK) { 246 throw new ERR_OUT_OF_RANGE('options.chunkSize', 247 `>= ${Z_MIN_CHUNK}`, chunkSize); 248 } 249 250 flush = checkRangesOrGetDefault( 251 opts.flush, 'options.flush', 252 Z_NO_FLUSH, Z_BLOCK, flush); 253 254 finishFlush = checkRangesOrGetDefault( 255 opts.finishFlush, 'options.finishFlush', 256 Z_NO_FLUSH, Z_BLOCK, finishFlush); 257 258 maxOutputLength = checkRangesOrGetDefault( 259 opts.maxOutputLength, 'options.maxOutputLength', 260 1, kMaxLength, kMaxLength); 261 262 if (opts.encoding || opts.objectMode || opts.writableObjectMode) { 263 opts = { ...opts }; 264 opts.encoding = null; 265 opts.objectMode = false; 266 opts.writableObjectMode = false; 267 } 268 } 269 270 Transform.call(this, opts); 271 this._hadError = false; 272 this.bytesWritten = 0; 273 this._handle = handle; 274 handle[owner_symbol] = this; 275 // Used by processCallback() and zlibOnError() 276 handle.onerror = zlibOnError; 277 this._outBuffer = Buffer.allocUnsafe(chunkSize); 278 this._outOffset = 0; 279 280 this._chunkSize = chunkSize; 281 this._defaultFlushFlag = flush; 282 this._finishFlushFlag = finishFlush; 283 this._defaultFullFlushFlag = fullFlush; 284 this.once('end', _close.bind(null, this)); 285 this._info = opts && opts.info; 286 this._maxOutputLength = maxOutputLength; 287} 288ObjectSetPrototypeOf(ZlibBase.prototype, Transform.prototype); 289ObjectSetPrototypeOf(ZlibBase, Transform); 290 291ObjectDefineProperty(ZlibBase.prototype, '_closed', { 292 configurable: true, 293 enumerable: true, 294 get() { 295 return !this._handle; 296 } 297}); 298 299// `bytesRead` made sense as a name when looking from the zlib engine's 300// perspective, but it is inconsistent with all other streams exposed by Node.js 301// that have this concept, where it stands for the number of bytes read 302// *from* the stream (that is, net.Socket/tls.Socket & file system streams). 303ObjectDefineProperty(ZlibBase.prototype, 'bytesRead', { 304 configurable: true, 305 enumerable: true, 306 get: deprecate(function() { 307 return this.bytesWritten; 308 }, 'zlib.bytesRead is deprecated and will change its meaning in the ' + 309 'future. Use zlib.bytesWritten instead.', 'DEP0108'), 310 set: deprecate(function(value) { 311 this.bytesWritten = value; 312 }, 'Setting zlib.bytesRead is deprecated. ' + 313 'This feature will be removed in the future.', 'DEP0108') 314}); 315 316ZlibBase.prototype.reset = function() { 317 if (!this._handle) 318 assert(false, 'zlib binding closed'); 319 return this._handle.reset(); 320}; 321 322// This is the _flush function called by the transform class, 323// internally, when the last chunk has been written. 324ZlibBase.prototype._flush = function(callback) { 325 this._transform(Buffer.alloc(0), '', callback); 326}; 327 328// If a flush is scheduled while another flush is still pending, a way to figure 329// out which one is the "stronger" flush is needed. 330// This is currently only used to figure out which flush flag to use for the 331// last chunk. 332// Roughly, the following holds: 333// Z_NO_FLUSH (< Z_TREES) < Z_BLOCK < Z_PARTIAL_FLUSH < 334// Z_SYNC_FLUSH < Z_FULL_FLUSH < Z_FINISH 335const flushiness = []; 336let i = 0; 337const kFlushFlagList = [Z_NO_FLUSH, Z_BLOCK, Z_PARTIAL_FLUSH, 338 Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH]; 339for (const flushFlag of kFlushFlagList) { 340 flushiness[flushFlag] = i++; 341} 342 343function maxFlush(a, b) { 344 return flushiness[a] > flushiness[b] ? a : b; 345} 346 347// Set up a list of 'special' buffers that can be written using .write() 348// from the .flush() code as a way of introducing flushing operations into the 349// write sequence. 350const kFlushBuffers = []; 351{ 352 const dummyArrayBuffer = new ArrayBuffer(); 353 for (const flushFlag of kFlushFlagList) { 354 kFlushBuffers[flushFlag] = Buffer.from(dummyArrayBuffer); 355 kFlushBuffers[flushFlag][kFlushFlag] = flushFlag; 356 } 357} 358 359ZlibBase.prototype.flush = function(kind, callback) { 360 if (typeof kind === 'function' || (kind === undefined && !callback)) { 361 callback = kind; 362 kind = this._defaultFullFlushFlag; 363 } 364 365 if (this.writableFinished) { 366 if (callback) 367 process.nextTick(callback); 368 } else if (this.writableEnded) { 369 if (callback) 370 this.once('end', callback); 371 } else { 372 this.write(kFlushBuffers[kind], '', callback); 373 } 374}; 375 376ZlibBase.prototype.close = function(callback) { 377 _close(this, callback); 378 this.destroy(); 379}; 380 381ZlibBase.prototype._destroy = function(err, callback) { 382 _close(this); 383 callback(err); 384}; 385 386ZlibBase.prototype._transform = function(chunk, encoding, cb) { 387 let flushFlag = this._defaultFlushFlag; 388 // We use a 'fake' zero-length chunk to carry information about flushes from 389 // the public API to the actual stream implementation. 390 if (typeof chunk[kFlushFlag] === 'number') { 391 flushFlag = chunk[kFlushFlag]; 392 } 393 394 // For the last chunk, also apply `_finishFlushFlag`. 395 if (this.writableEnded && this.writableLength === chunk.byteLength) { 396 flushFlag = maxFlush(flushFlag, this._finishFlushFlag); 397 } 398 processChunk(this, chunk, flushFlag, cb); 399}; 400 401ZlibBase.prototype._processChunk = function(chunk, flushFlag, cb) { 402 // _processChunk() is left for backwards compatibility 403 if (typeof cb === 'function') 404 processChunk(this, chunk, flushFlag, cb); 405 else 406 return processChunkSync(this, chunk, flushFlag); 407}; 408 409function processChunkSync(self, chunk, flushFlag) { 410 let availInBefore = chunk.byteLength; 411 let availOutBefore = self._chunkSize - self._outOffset; 412 let inOff = 0; 413 let availOutAfter; 414 let availInAfter; 415 416 let buffers = null; 417 let nread = 0; 418 let inputRead = 0; 419 const state = self._writeState; 420 const handle = self._handle; 421 let buffer = self._outBuffer; 422 let offset = self._outOffset; 423 const chunkSize = self._chunkSize; 424 425 let error; 426 self.on('error', function onError(er) { 427 error = er; 428 }); 429 430 while (true) { 431 handle.writeSync(flushFlag, 432 chunk, // in 433 inOff, // in_off 434 availInBefore, // in_len 435 buffer, // out 436 offset, // out_off 437 availOutBefore); // out_len 438 if (error) 439 throw error; 440 441 availOutAfter = state[0]; 442 availInAfter = state[1]; 443 444 const inDelta = (availInBefore - availInAfter); 445 inputRead += inDelta; 446 447 const have = availOutBefore - availOutAfter; 448 if (have > 0) { 449 const out = buffer.slice(offset, offset + have); 450 offset += have; 451 if (!buffers) 452 buffers = [out]; 453 else 454 buffers.push(out); 455 nread += out.byteLength; 456 457 if (nread > self._maxOutputLength) { 458 _close(self); 459 throw new ERR_BUFFER_TOO_LARGE(self._maxOutputLength); 460 } 461 462 } else { 463 assert(have === 0, 'have should not go down'); 464 } 465 466 // Exhausted the output buffer, or used all the input create a new one. 467 if (availOutAfter === 0 || offset >= chunkSize) { 468 availOutBefore = chunkSize; 469 offset = 0; 470 buffer = Buffer.allocUnsafe(chunkSize); 471 } 472 473 if (availOutAfter === 0) { 474 // Not actually done. Need to reprocess. 475 // Also, update the availInBefore to the availInAfter value, 476 // so that if we have to hit it a third (fourth, etc.) time, 477 // it'll have the correct byte counts. 478 inOff += inDelta; 479 availInBefore = availInAfter; 480 } else { 481 break; 482 } 483 } 484 485 self.bytesWritten = inputRead; 486 _close(self); 487 488 if (nread === 0) 489 return Buffer.alloc(0); 490 491 return (buffers.length === 1 ? buffers[0] : Buffer.concat(buffers, nread)); 492} 493 494function processChunk(self, chunk, flushFlag, cb) { 495 const handle = self._handle; 496 if (!handle) return process.nextTick(cb); 497 498 handle.buffer = chunk; 499 handle.cb = cb; 500 handle.availOutBefore = self._chunkSize - self._outOffset; 501 handle.availInBefore = chunk.byteLength; 502 handle.inOff = 0; 503 handle.flushFlag = flushFlag; 504 505 handle.write(flushFlag, 506 chunk, // in 507 0, // in_off 508 handle.availInBefore, // in_len 509 self._outBuffer, // out 510 self._outOffset, // out_off 511 handle.availOutBefore); // out_len 512} 513 514function processCallback() { 515 // This callback's context (`this`) is the `_handle` (ZCtx) object. It is 516 // important to null out the values once they are no longer needed since 517 // `_handle` can stay in memory long after the buffer is needed. 518 const handle = this; 519 const self = this[owner_symbol]; 520 const state = self._writeState; 521 522 if (self._hadError || self.destroyed) { 523 this.buffer = null; 524 this.cb(); 525 return; 526 } 527 528 const availOutAfter = state[0]; 529 const availInAfter = state[1]; 530 531 const inDelta = handle.availInBefore - availInAfter; 532 self.bytesWritten += inDelta; 533 534 const have = handle.availOutBefore - availOutAfter; 535 if (have > 0) { 536 const out = self._outBuffer.slice(self._outOffset, self._outOffset + have); 537 self._outOffset += have; 538 self.push(out); 539 } else { 540 assert(have === 0, 'have should not go down'); 541 } 542 543 if (self.destroyed) { 544 this.cb(); 545 return; 546 } 547 548 // Exhausted the output buffer, or used all the input create a new one. 549 if (availOutAfter === 0 || self._outOffset >= self._chunkSize) { 550 handle.availOutBefore = self._chunkSize; 551 self._outOffset = 0; 552 self._outBuffer = Buffer.allocUnsafe(self._chunkSize); 553 } 554 555 if (availOutAfter === 0) { 556 // Not actually done. Need to reprocess. 557 // Also, update the availInBefore to the availInAfter value, 558 // so that if we have to hit it a third (fourth, etc.) time, 559 // it'll have the correct byte counts. 560 handle.inOff += inDelta; 561 handle.availInBefore = availInAfter; 562 563 this.write(handle.flushFlag, 564 this.buffer, // in 565 handle.inOff, // in_off 566 handle.availInBefore, // in_len 567 self._outBuffer, // out 568 self._outOffset, // out_off 569 self._chunkSize); // out_len 570 return; 571 } 572 573 if (availInAfter > 0) { 574 // If we have more input that should be written, but we also have output 575 // space available, that means that the compression library was not 576 // interested in receiving more data, and in particular that the input 577 // stream has ended early. 578 // This applies to streams where we don't check data past the end of 579 // what was consumed; that is, everything except Gunzip/Unzip. 580 self.push(null); 581 } 582 583 // Finished with the chunk. 584 this.buffer = null; 585 this.cb(); 586} 587 588function _close(engine, callback) { 589 if (callback) 590 process.nextTick(callback); 591 592 // Caller may invoke .close after a zlib error (which will null _handle). 593 if (!engine._handle) 594 return; 595 596 engine._handle.close(); 597 engine._handle = null; 598} 599 600const zlibDefaultOpts = { 601 flush: Z_NO_FLUSH, 602 finishFlush: Z_FINISH, 603 fullFlush: Z_FULL_FLUSH 604}; 605// Base class for all streams actually backed by zlib and using zlib-specific 606// parameters. 607function Zlib(opts, mode) { 608 let windowBits = Z_DEFAULT_WINDOWBITS; 609 let level = Z_DEFAULT_COMPRESSION; 610 let memLevel = Z_DEFAULT_MEMLEVEL; 611 let strategy = Z_DEFAULT_STRATEGY; 612 let dictionary; 613 614 if (opts) { 615 // windowBits is special. On the compression side, 0 is an invalid value. 616 // But on the decompression side, a value of 0 for windowBits tells zlib 617 // to use the window size in the zlib header of the compressed stream. 618 if ((opts.windowBits == null || opts.windowBits === 0) && 619 (mode === INFLATE || 620 mode === GUNZIP || 621 mode === UNZIP)) { 622 windowBits = 0; 623 } else { 624 // `{ windowBits: 8 }` is valid for deflate but not gzip. 625 const min = Z_MIN_WINDOWBITS + (mode === GZIP ? 1 : 0); 626 windowBits = checkRangesOrGetDefault( 627 opts.windowBits, 'options.windowBits', 628 min, Z_MAX_WINDOWBITS, Z_DEFAULT_WINDOWBITS); 629 } 630 631 level = checkRangesOrGetDefault( 632 opts.level, 'options.level', 633 Z_MIN_LEVEL, Z_MAX_LEVEL, Z_DEFAULT_COMPRESSION); 634 635 memLevel = checkRangesOrGetDefault( 636 opts.memLevel, 'options.memLevel', 637 Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_MEMLEVEL); 638 639 strategy = checkRangesOrGetDefault( 640 opts.strategy, 'options.strategy', 641 Z_DEFAULT_STRATEGY, Z_FIXED, Z_DEFAULT_STRATEGY); 642 643 dictionary = opts.dictionary; 644 if (dictionary !== undefined && !isArrayBufferView(dictionary)) { 645 if (isAnyArrayBuffer(dictionary)) { 646 dictionary = Buffer.from(dictionary); 647 } else { 648 throw new ERR_INVALID_ARG_TYPE( 649 'options.dictionary', 650 ['Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'], 651 dictionary 652 ); 653 } 654 } 655 } 656 657 const handle = new binding.Zlib(mode); 658 // Ideally, we could let ZlibBase() set up _writeState. I haven't been able 659 // to come up with a good solution that doesn't break our internal API, 660 // and with it all supported npm versions at the time of writing. 661 this._writeState = new Uint32Array(2); 662 handle.init(windowBits, 663 level, 664 memLevel, 665 strategy, 666 this._writeState, 667 processCallback, 668 dictionary); 669 670 ZlibBase.call(this, opts, mode, handle, zlibDefaultOpts); 671 672 this._level = level; 673 this._strategy = strategy; 674} 675ObjectSetPrototypeOf(Zlib.prototype, ZlibBase.prototype); 676ObjectSetPrototypeOf(Zlib, ZlibBase); 677 678// This callback is used by `.params()` to wait until a full flush happened 679// before adjusting the parameters. In particular, the call to the native 680// `params()` function should not happen while a write is currently in progress 681// on the threadpool. 682function paramsAfterFlushCallback(level, strategy, callback) { 683 assert(this._handle, 'zlib binding closed'); 684 this._handle.params(level, strategy); 685 if (!this._hadError) { 686 this._level = level; 687 this._strategy = strategy; 688 if (callback) callback(); 689 } 690} 691 692Zlib.prototype.params = function params(level, strategy, callback) { 693 checkRangesOrGetDefault(level, 'level', Z_MIN_LEVEL, Z_MAX_LEVEL); 694 checkRangesOrGetDefault(strategy, 'strategy', Z_DEFAULT_STRATEGY, Z_FIXED); 695 696 if (this._level !== level || this._strategy !== strategy) { 697 this.flush(Z_SYNC_FLUSH, 698 paramsAfterFlushCallback.bind(this, level, strategy, callback)); 699 } else { 700 process.nextTick(callback); 701 } 702}; 703 704// generic zlib 705// minimal 2-byte header 706function Deflate(opts) { 707 if (!(this instanceof Deflate)) 708 return new Deflate(opts); 709 Zlib.call(this, opts, DEFLATE); 710} 711ObjectSetPrototypeOf(Deflate.prototype, Zlib.prototype); 712ObjectSetPrototypeOf(Deflate, Zlib); 713 714function Inflate(opts) { 715 if (!(this instanceof Inflate)) 716 return new Inflate(opts); 717 Zlib.call(this, opts, INFLATE); 718} 719ObjectSetPrototypeOf(Inflate.prototype, Zlib.prototype); 720ObjectSetPrototypeOf(Inflate, Zlib); 721 722function Gzip(opts) { 723 if (!(this instanceof Gzip)) 724 return new Gzip(opts); 725 Zlib.call(this, opts, GZIP); 726} 727ObjectSetPrototypeOf(Gzip.prototype, Zlib.prototype); 728ObjectSetPrototypeOf(Gzip, Zlib); 729 730function Gunzip(opts) { 731 if (!(this instanceof Gunzip)) 732 return new Gunzip(opts); 733 Zlib.call(this, opts, GUNZIP); 734} 735ObjectSetPrototypeOf(Gunzip.prototype, Zlib.prototype); 736ObjectSetPrototypeOf(Gunzip, Zlib); 737 738function DeflateRaw(opts) { 739 if (opts && opts.windowBits === 8) opts.windowBits = 9; 740 if (!(this instanceof DeflateRaw)) 741 return new DeflateRaw(opts); 742 Zlib.call(this, opts, DEFLATERAW); 743} 744ObjectSetPrototypeOf(DeflateRaw.prototype, Zlib.prototype); 745ObjectSetPrototypeOf(DeflateRaw, Zlib); 746 747function InflateRaw(opts) { 748 if (!(this instanceof InflateRaw)) 749 return new InflateRaw(opts); 750 Zlib.call(this, opts, INFLATERAW); 751} 752ObjectSetPrototypeOf(InflateRaw.prototype, Zlib.prototype); 753ObjectSetPrototypeOf(InflateRaw, Zlib); 754 755function Unzip(opts) { 756 if (!(this instanceof Unzip)) 757 return new Unzip(opts); 758 Zlib.call(this, opts, UNZIP); 759} 760ObjectSetPrototypeOf(Unzip.prototype, Zlib.prototype); 761ObjectSetPrototypeOf(Unzip, Zlib); 762 763function createConvenienceMethod(ctor, sync) { 764 if (sync) { 765 return function syncBufferWrapper(buffer, opts) { 766 return zlibBufferSync(new ctor(opts), buffer); 767 }; 768 } 769 return function asyncBufferWrapper(buffer, opts, callback) { 770 if (typeof opts === 'function') { 771 callback = opts; 772 opts = {}; 773 } 774 return zlibBuffer(new ctor(opts), buffer, callback); 775 }; 776} 777 778const kMaxBrotliParam = MathMax(...ObjectKeys(constants).map((key) => { 779 return key.startsWith('BROTLI_PARAM_') ? constants[key] : 0; 780})); 781 782const brotliInitParamsArray = new Uint32Array(kMaxBrotliParam + 1); 783 784const brotliDefaultOpts = { 785 flush: BROTLI_OPERATION_PROCESS, 786 finishFlush: BROTLI_OPERATION_FINISH, 787 fullFlush: BROTLI_OPERATION_FLUSH 788}; 789function Brotli(opts, mode) { 790 assert(mode === BROTLI_DECODE || mode === BROTLI_ENCODE); 791 792 brotliInitParamsArray.fill(-1); 793 if (opts && opts.params) { 794 for (const origKey of ObjectKeys(opts.params)) { 795 const key = +origKey; 796 if (NumberIsNaN(key) || key < 0 || key > kMaxBrotliParam || 797 (brotliInitParamsArray[key] | 0) !== -1) { 798 throw new ERR_BROTLI_INVALID_PARAM(origKey); 799 } 800 801 const value = opts.params[origKey]; 802 if (typeof value !== 'number' && typeof value !== 'boolean') { 803 throw new ERR_INVALID_ARG_TYPE('options.params[key]', 804 'number', opts.params[origKey]); 805 } 806 brotliInitParamsArray[key] = value; 807 } 808 } 809 810 const handle = mode === BROTLI_DECODE ? 811 new binding.BrotliDecoder(mode) : new binding.BrotliEncoder(mode); 812 813 this._writeState = new Uint32Array(2); 814 // TODO(addaleax): Sometimes we generate better error codes in C++ land, 815 // e.g. ERR_BROTLI_PARAM_SET_FAILED -- it's hard to access them with 816 // the current bindings setup, though. 817 if (!handle.init(brotliInitParamsArray, 818 this._writeState, 819 processCallback)) { 820 throw new ERR_ZLIB_INITIALIZATION_FAILED(); 821 } 822 823 ZlibBase.call(this, opts, mode, handle, brotliDefaultOpts); 824} 825ObjectSetPrototypeOf(Brotli.prototype, Zlib.prototype); 826ObjectSetPrototypeOf(Brotli, Zlib); 827 828function BrotliCompress(opts) { 829 if (!(this instanceof BrotliCompress)) 830 return new BrotliCompress(opts); 831 Brotli.call(this, opts, BROTLI_ENCODE); 832} 833ObjectSetPrototypeOf(BrotliCompress.prototype, Brotli.prototype); 834ObjectSetPrototypeOf(BrotliCompress, Brotli); 835 836function BrotliDecompress(opts) { 837 if (!(this instanceof BrotliDecompress)) 838 return new BrotliDecompress(opts); 839 Brotli.call(this, opts, BROTLI_DECODE); 840} 841ObjectSetPrototypeOf(BrotliDecompress.prototype, Brotli.prototype); 842ObjectSetPrototypeOf(BrotliDecompress, Brotli); 843 844 845function createProperty(ctor) { 846 return { 847 configurable: true, 848 enumerable: true, 849 value: function(options) { 850 return new ctor(options); 851 } 852 }; 853} 854 855// Legacy alias on the C++ wrapper object. This is not public API, so we may 856// want to runtime-deprecate it at some point. There's no hurry, though. 857ObjectDefineProperty(binding.Zlib.prototype, 'jsref', { 858 get() { return this[owner_symbol]; }, 859 set(v) { return this[owner_symbol] = v; } 860}); 861 862module.exports = { 863 Deflate, 864 Inflate, 865 Gzip, 866 Gunzip, 867 DeflateRaw, 868 InflateRaw, 869 Unzip, 870 BrotliCompress, 871 BrotliDecompress, 872 873 // Convenience methods. 874 // compress/decompress a string or buffer in one step. 875 deflate: createConvenienceMethod(Deflate, false), 876 deflateSync: createConvenienceMethod(Deflate, true), 877 gzip: createConvenienceMethod(Gzip, false), 878 gzipSync: createConvenienceMethod(Gzip, true), 879 deflateRaw: createConvenienceMethod(DeflateRaw, false), 880 deflateRawSync: createConvenienceMethod(DeflateRaw, true), 881 unzip: createConvenienceMethod(Unzip, false), 882 unzipSync: createConvenienceMethod(Unzip, true), 883 inflate: createConvenienceMethod(Inflate, false), 884 inflateSync: createConvenienceMethod(Inflate, true), 885 gunzip: createConvenienceMethod(Gunzip, false), 886 gunzipSync: createConvenienceMethod(Gunzip, true), 887 inflateRaw: createConvenienceMethod(InflateRaw, false), 888 inflateRawSync: createConvenienceMethod(InflateRaw, true), 889 brotliCompress: createConvenienceMethod(BrotliCompress, false), 890 brotliCompressSync: createConvenienceMethod(BrotliCompress, true), 891 brotliDecompress: createConvenienceMethod(BrotliDecompress, false), 892 brotliDecompressSync: createConvenienceMethod(BrotliDecompress, true), 893}; 894 895ObjectDefineProperties(module.exports, { 896 createDeflate: createProperty(Deflate), 897 createInflate: createProperty(Inflate), 898 createDeflateRaw: createProperty(DeflateRaw), 899 createInflateRaw: createProperty(InflateRaw), 900 createGzip: createProperty(Gzip), 901 createGunzip: createProperty(Gunzip), 902 createUnzip: createProperty(Unzip), 903 createBrotliCompress: createProperty(BrotliCompress), 904 createBrotliDecompress: createProperty(BrotliDecompress), 905 constants: { 906 configurable: false, 907 enumerable: true, 908 value: constants 909 }, 910 codes: { 911 enumerable: true, 912 writable: false, 913 value: ObjectFreeze(codes) 914 } 915}); 916 917// These should be considered deprecated 918// expose all the zlib constants 919for (const bkey of ObjectKeys(constants)) { 920 if (bkey.startsWith('BROTLI')) continue; 921 ObjectDefineProperty(module.exports, bkey, { 922 enumerable: false, value: constants[bkey], writable: false 923 }); 924} 925