• 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  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