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