Skip to content

Commit 53e9c85

Browse files
addaleaxMylesBorins
authored andcommitted
zlib: add brotli support
Refs: #20458 Co-authored-by: Hackzzila <[email protected]> Backport-PR-URL: #27681 PR-URL: #24938 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Jan Krems <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Myles Borins <[email protected]> Reviewed-By: Ali Ijaz Sheikh <[email protected]> Reviewed-By: Daniel Bevenius <[email protected]>
1 parent ce6fec5 commit 53e9c85

File tree

8 files changed

+543
-35
lines changed

8 files changed

+543
-35
lines changed

doc/api/errors.md

+10
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,16 @@ An attempt was made to register something that is not a function as an
624624
The type of an asynchronous resource was invalid. Note that users are also able
625625
to define their own types if using the public embedder API.
626626

627+
<a id="ERR_BROTLI_INVALID_PARAM"></a>
628+
### ERR_BROTLI_INVALID_PARAM
629+
630+
An invalid parameter key was passed during construction of a Brotli stream.
631+
632+
<a id="ERR_BROTLI_COMPRESSION_FAILED">
633+
### ERR_BROTLI_COMPRESSION_FAILED
634+
635+
Data passed to a Brotli stream was not successfully compressed.
636+
627637
<a id="ERR_BUFFER_OUT_OF_BOUNDS"></a>
628638
### ERR_BUFFER_OUT_OF_BOUNDS
629639

lib/internal/errors.js

+1
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError);
512512
E('ERR_ASSERTION', '%s', Error);
513513
E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError);
514514
E('ERR_ASYNC_TYPE', 'Invalid name for async "type": %s', TypeError);
515+
E('ERR_BROTLI_INVALID_PARAM', '%s is not a valid Brotli parameter', RangeError);
515516
E('ERR_BUFFER_OUT_OF_BOUNDS',
516517
// Using a default argument here is important so the argument is not counted
517518
// towards `Function#length`.

lib/zlib.js

+89-5
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
'use strict';
2323

2424
const {
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;
3031
const Transform = require('_stream_transform');
3132
const {
@@ -46,11 +47,18 @@ const { owner_symbol } = require('internal/async_hooks').symbols;
4647

4748
const constants = process.binding('constants').zlib;
4849
const {
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+
726801
function 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

770851
Object.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, {
792875
const bkeys = Object.keys(constants);
793876
for (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
});

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
'node_shared%': 'false',
1313
'force_dynamic_crt%': 0,
1414
'node_module_version%': '',
15+
'node_shared_brotli%': 'false',
1516
'node_shared_zlib%': 'false',
1617
'node_shared_http_parser%': 'false',
1718
'node_shared_cares%': 'false',

node.gypi

+4
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@
205205
'dependencies': [ 'deps/nghttp2/nghttp2.gyp:nghttp2' ],
206206
}],
207207

208+
[ 'node_shared_brotli=="false"', {
209+
'dependencies': [ 'deps/brotli/brotli.gyp:brotli' ],
210+
}],
211+
208212
[ 'OS=="mac"', {
209213
# linking Corefoundation is needed since certain OSX debugging tools
210214
# like Instruments require it for some features

src/node.cc

+12
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353

5454
#include "ares.h"
5555
#include "async_wrap-inl.h"
56+
#include "brotli/encode.h"
5657
#include "env-inl.h"
5758
#include "handle_wrap.h"
5859
#include "http_parser.h"
@@ -1723,6 +1724,14 @@ void SetupProcessObject(Environment* env,
17231724
NODE_STRINGIFY(HTTP_PARSER_VERSION_MINOR)
17241725
"."
17251726
NODE_STRINGIFY(HTTP_PARSER_VERSION_PATCH);
1727+
1728+
const std::string brotli_version =
1729+
std::to_string(BrotliEncoderVersion() >> 24) +
1730+
"." +
1731+
std::to_string((BrotliEncoderVersion() & 0xFFF000) >> 12) +
1732+
"." +
1733+
std::to_string(BrotliEncoderVersion() & 0xFFF);
1734+
17261735
READONLY_PROPERTY(versions,
17271736
"http_parser",
17281737
FIXED_ONE_BYTE_STRING(env->isolate(), http_parser_version));
@@ -1739,6 +1748,9 @@ void SetupProcessObject(Environment* env,
17391748
READONLY_PROPERTY(versions,
17401749
"zlib",
17411750
FIXED_ONE_BYTE_STRING(env->isolate(), ZLIB_VERSION));
1751+
READONLY_PROPERTY(versions,
1752+
"brotli",
1753+
OneByteString(env->isolate(), brotli_version.c_str()));
17421754
READONLY_PROPERTY(versions,
17431755
"ares",
17441756
FIXED_ONE_BYTE_STRING(env->isolate(), ARES_VERSION_STR));

0 commit comments

Comments
 (0)