2222'use strict';
2323
2424const {
25+ ERR_BROTLI_INVALID_PARAM,
2526 ERR_BUFFER_TOO_LARGE,
2627 ERR_INVALID_ARG_TYPE,
2728 ERR_OUT_OF_RANGE,
28- ERR_ZLIB_INITIALIZATION_FAILED
29+ ERR_ZLIB_INITIALIZATION_FAILED,
2930} = require('internal/errors').codes;
3031const Transform = require('_stream_transform');
3132const {
@@ -46,11 +47,18 @@ const { owner_symbol } = require('internal/async_hooks').symbols;
4647
4748const constants = process.binding('constants').zlib;
4849const {
50+ // Zlib flush levels
4951 Z_NO_FLUSH, Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH,
52+ // Zlib option values
5053 Z_MIN_CHUNK, Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_MIN_LEVEL, Z_MAX_LEVEL,
5154 Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_CHUNK, Z_DEFAULT_COMPRESSION,
5255 Z_DEFAULT_STRATEGY, Z_DEFAULT_WINDOWBITS, Z_DEFAULT_MEMLEVEL, Z_FIXED,
53- DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP
56+ // Node's compression stream modes (node_zlib_mode)
57+ DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP,
58+ BROTLI_DECODE, BROTLI_ENCODE,
59+ // Brotli operations (~flush levels)
60+ BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_FLUSH,
61+ BROTLI_OPERATION_FINISH
5462} = constants;
5563
5664// translation table for return codes.
@@ -211,7 +219,7 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
211219 // The ZlibBase class is not exported to user land, the mode should only be
212220 // passed in by us.
213221 assert(typeof mode === 'number');
214- assert(mode >= DEFLATE && mode <= UNZIP );
222+ assert(mode >= DEFLATE && mode <= BROTLI_ENCODE );
215223
216224 if (opts) {
217225 chunkSize = opts.chunkSize;
@@ -478,7 +486,7 @@ function processCallback() {
478486 // important to null out the values once they are no longer needed since
479487 // `_handle` can stay in memory long after the buffer is needed.
480488 var handle = this;
481- var self = this.jsref ;
489+ var self = this[owner_symbol] ;
482490 var state = self._writeState;
483491
484492 if (self._hadError) {
@@ -619,6 +627,9 @@ function Zlib(opts, mode) {
619627 this._writeState,
620628 processCallback,
621629 dictionary)) {
630+ // TODO(addaleax): Sometimes we generate better error codes in C++ land,
631+ // e.g. ERR_BROTLI_PARAM_SET_FAILED -- it's hard to access them with
632+ // the current bindings setup, though.
622633 throw new ERR_ZLIB_INITIALIZATION_FAILED();
623634 }
624635
@@ -723,6 +734,70 @@ function createConvenienceMethod(ctor, sync) {
723734 }
724735}
725736
737+ const kMaxBrotliParam = Math.max(...Object.keys(constants).map((key) => {
738+ return key.startsWith('BROTLI_PARAM_') ? constants[key] : 0;
739+ }));
740+
741+ const brotliInitParamsArray = new Uint32Array(kMaxBrotliParam + 1);
742+
743+ const brotliDefaultOpts = {
744+ flush: BROTLI_OPERATION_PROCESS,
745+ finishFlush: BROTLI_OPERATION_FINISH,
746+ fullFlush: BROTLI_OPERATION_FLUSH
747+ };
748+ function Brotli(opts, mode) {
749+ assert(mode === BROTLI_DECODE || mode === BROTLI_ENCODE);
750+
751+ brotliInitParamsArray.fill(-1);
752+ if (opts && opts.params) {
753+ for (const origKey of Object.keys(opts.params)) {
754+ const key = +origKey;
755+ if (Number.isNaN(key) || key < 0 || key > kMaxBrotliParam ||
756+ (brotliInitParamsArray[key] | 0) !== -1) {
757+ throw new ERR_BROTLI_INVALID_PARAM(origKey);
758+ }
759+
760+ const value = opts.params[origKey];
761+ if (typeof value !== 'number' && typeof value !== 'boolean') {
762+ throw new ERR_INVALID_ARG_TYPE('options.params[key]',
763+ 'number', opts.params[origKey]);
764+ }
765+ brotliInitParamsArray[key] = value;
766+ }
767+ }
768+
769+ const handle = mode === BROTLI_DECODE ?
770+ new binding.BrotliDecoder(mode) : new binding.BrotliEncoder(mode);
771+
772+ this._writeState = new Uint32Array(2);
773+ if (!handle.init(brotliInitParamsArray,
774+ this._writeState,
775+ processCallback)) {
776+ throw new ERR_ZLIB_INITIALIZATION_FAILED();
777+ }
778+
779+ ZlibBase.call(this, opts, mode, handle, brotliDefaultOpts);
780+ }
781+ Object.setPrototypeOf(Brotli.prototype, Zlib.prototype);
782+ Object.setPrototypeOf(Brotli, Zlib);
783+
784+ function BrotliCompress(opts) {
785+ if (!(this instanceof BrotliCompress))
786+ return new BrotliCompress(opts);
787+ Brotli.call(this, opts, BROTLI_ENCODE);
788+ }
789+ Object.setPrototypeOf(BrotliCompress.prototype, Brotli.prototype);
790+ Object.setPrototypeOf(BrotliCompress, Brotli);
791+
792+ function BrotliDecompress(opts) {
793+ if (!(this instanceof BrotliDecompress))
794+ return new BrotliDecompress(opts);
795+ Brotli.call(this, opts, BROTLI_DECODE);
796+ }
797+ Object.setPrototypeOf(BrotliDecompress.prototype, Brotli.prototype);
798+ Object.setPrototypeOf(BrotliDecompress, Brotli);
799+
800+
726801function createProperty(ctor) {
727802 return {
728803 configurable: true,
@@ -748,6 +823,8 @@ module.exports = {
748823 DeflateRaw,
749824 InflateRaw,
750825 Unzip,
826+ BrotliCompress,
827+ BrotliDecompress,
751828
752829 // Convenience methods.
753830 // compress/decompress a string or buffer in one step.
@@ -764,7 +841,11 @@ module.exports = {
764841 gunzip: createConvenienceMethod(Gunzip, false),
765842 gunzipSync: createConvenienceMethod(Gunzip, true),
766843 inflateRaw: createConvenienceMethod(InflateRaw, false),
767- inflateRawSync: createConvenienceMethod(InflateRaw, true)
844+ inflateRawSync: createConvenienceMethod(InflateRaw, true),
845+ brotliCompress: createConvenienceMethod(BrotliCompress, false),
846+ brotliCompressSync: createConvenienceMethod(BrotliCompress, true),
847+ brotliDecompress: createConvenienceMethod(BrotliDecompress, false),
848+ brotliDecompressSync: createConvenienceMethod(BrotliDecompress, true),
768849};
769850
770851Object.defineProperties(module.exports, {
@@ -775,6 +856,8 @@ Object.defineProperties(module.exports, {
775856 createGzip: createProperty(Gzip),
776857 createGunzip: createProperty(Gunzip),
777858 createUnzip: createProperty(Unzip),
859+ createBrotliCompress: createProperty(BrotliCompress),
860+ createBrotliDecompress: createProperty(BrotliDecompress),
778861 constants: {
779862 configurable: false,
780863 enumerable: true,
@@ -792,6 +875,7 @@ Object.defineProperties(module.exports, {
792875const bkeys = Object.keys(constants);
793876for (var bk = 0; bk < bkeys.length; bk++) {
794877 var bkey = bkeys[bk];
878+ if (bkey.startsWith('BROTLI')) continue;
795879 Object.defineProperty(module.exports, bkey, {
796880 enumerable: true, value: constants[bkey], writable: false
797881 });
0 commit comments