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