Skip to content

Commit c1f625f

Browse files
rosaxxnycodebytere
authored andcommitted
zlib: add maxOutputLength option
Fixes: #27253 PR-URL: #33516 Reviewed-By: Anna Henningsen <[email protected]>
1 parent 3520a13 commit c1f625f

6 files changed

+59
-12
lines changed

doc/api/zlib.md

+13
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,9 @@ These advanced options are available for controlling decompression:
486486
<!-- YAML
487487
added: v0.11.1
488488
changes:
489+
- version: REPLACEME
490+
pr-url: https://github.com/nodejs/node/pull/33516
491+
description: The `maxOutputLength` option is supported now.
489492
- version: v9.4.0
490493
pr-url: https://github.com/nodejs/node/pull/16042
491494
description: The `dictionary` option can be an `ArrayBuffer`.
@@ -514,13 +517,19 @@ ignored by the decompression classes.
514517
* `dictionary` {Buffer|TypedArray|DataView|ArrayBuffer} (deflate/inflate only,
515518
empty dictionary by default)
516519
* `info` {boolean} (If `true`, returns an object with `buffer` and `engine`.)
520+
* `maxOutputLength` {integer} Limits output size when using
521+
[convenience methods][]. **Default:** [`buffer.kMaxLength`][]
517522

518523
See the [`deflateInit2` and `inflateInit2`][] documentation for more
519524
information.
520525

521526
## Class: `BrotliOptions`
522527
<!-- YAML
523528
added: v11.7.0
529+
changes:
530+
- version: REPLACEME
531+
pr-url: https://github.com/nodejs/node/pull/33516
532+
description: The `maxOutputLength` option is supported now.
524533
-->
525534

526535
<!--type=misc-->
@@ -531,6 +540,8 @@ Each Brotli-based class takes an `options` object. All options are optional.
531540
* `finishFlush` {integer} **Default:** `zlib.constants.BROTLI_OPERATION_FINISH`
532541
* `chunkSize` {integer} **Default:** `16 * 1024`
533542
* `params` {Object} Key-value object containing indexed [Brotli parameters][].
543+
* `maxOutputLength` {integer} Limits output size when using
544+
[convenience methods][]. **Default:** [`buffer.kMaxLength`][]
534545

535546
For example:
536547

@@ -1160,6 +1171,7 @@ Decompress a chunk of data with [`Unzip`][].
11601171
[`BrotliCompress`]: #zlib_class_zlib_brotlicompress
11611172
[`BrotliDecompress`]: #zlib_class_zlib_brotlidecompress
11621173
[`Buffer`]: buffer.html#buffer_class_buffer
1174+
[`buffer.kMaxLength`]: buffer.html#buffer_buffer_kmaxlength
11631175
[`Content-Encoding`]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
11641176
[`DataView`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView
11651177
[`DeflateRaw`]: #zlib_class_zlib_deflateraw
@@ -1177,5 +1189,6 @@ Decompress a chunk of data with [`Unzip`][].
11771189
[Memory Usage Tuning]: #zlib_memory_usage_tuning
11781190
[RFC 7932]: https://www.rfc-editor.org/rfc/rfc7932.txt
11791191
[Streams API]: stream.md
1192+
[convenience methods]: #zlib_convenience_methods
11801193
[zlib documentation]: https://zlib.net/manual.html#Constants
11811194
[zlib.createGzip example]: #zlib_zlib

lib/internal/errors.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ const kTypes = [
4747
'symbol'
4848
];
4949

50-
const { kMaxLength } = internalBinding('buffer');
51-
5250
const MainContextError = Error;
5351
const ErrorToString = Error.prototype.toString;
5452
const overrideStackTrace = new WeakMap();
@@ -768,7 +766,7 @@ E('ERR_BUFFER_OUT_OF_BOUNDS',
768766
return 'Attempt to access memory outside buffer bounds';
769767
}, RangeError);
770768
E('ERR_BUFFER_TOO_LARGE',
771-
`Cannot create a Buffer larger than 0x${kMaxLength.toString(16)} bytes`,
769+
'Cannot create a Buffer larger than %s bytes',
772770
RangeError);
773771
E('ERR_CANNOT_WATCH_SIGINT', 'Cannot watch for SIGINT signals', Error);
774772
E('ERR_CHILD_CLOSED_BEFORE_REPLY',

lib/zlib.js

+18-7
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ function zlibBufferOnData(chunk) {
124124
else
125125
this.buffers.push(chunk);
126126
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+
}
127132
}
128133

129134
function zlibBufferOnError(err) {
@@ -134,9 +139,7 @@ function zlibBufferOnError(err) {
134139
function zlibBufferOnEnd() {
135140
let buf;
136141
let err;
137-
if (this.nread >= kMaxLength) {
138-
err = new ERR_BUFFER_TOO_LARGE();
139-
} else if (this.nread === 0) {
142+
if (this.nread === 0) {
140143
buf = Buffer.alloc(0);
141144
} else {
142145
const bufs = this.buffers;
@@ -231,6 +234,7 @@ const checkRangesOrGetDefault = hideStackFrames(
231234
// The base class for all Zlib-style streams.
232235
function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
233236
let chunkSize = Z_DEFAULT_CHUNK;
237+
let maxOutputLength = kMaxLength;
234238
// The ZlibBase class is not exported to user land, the mode should only be
235239
// passed in by us.
236240
assert(typeof mode === 'number');
@@ -253,6 +257,10 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
253257
opts.finishFlush, 'options.finishFlush',
254258
Z_NO_FLUSH, Z_BLOCK, finishFlush);
255259

260+
maxOutputLength = checkRangesOrGetDefault(
261+
opts.maxOutputLength, 'options.maxOutputLength',
262+
1, kMaxLength, kMaxLength);
263+
256264
if (opts.encoding || opts.objectMode || opts.writableObjectMode) {
257265
opts = { ...opts };
258266
opts.encoding = null;
@@ -276,6 +284,7 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
276284
this._finishFlushFlag = finishFlush;
277285
this._defaultFullFlushFlag = fullFlush;
278286
this._info = opts && opts.info;
287+
this._maxOutputLength = maxOutputLength;
279288
}
280289
ObjectSetPrototypeOf(ZlibBase.prototype, Transform.prototype);
281290
ObjectSetPrototypeOf(ZlibBase, Transform);
@@ -450,6 +459,12 @@ function processChunkSync(self, chunk, flushFlag) {
450459
else
451460
buffers.push(out);
452461
nread += out.byteLength;
462+
463+
if (nread > self._maxOutputLength) {
464+
_close(self);
465+
throw new ERR_BUFFER_TOO_LARGE(self._maxOutputLength);
466+
}
467+
453468
} else {
454469
assert(have === 0, 'have should not go down');
455470
}
@@ -476,10 +491,6 @@ function processChunkSync(self, chunk, flushFlag) {
476491
self.bytesWritten = inputRead;
477492
_close(self);
478493

479-
if (nread >= kMaxLength) {
480-
throw new ERR_BUFFER_TOO_LARGE();
481-
}
482-
483494
if (nread === 0)
484495
return Buffer.alloc(0);
485496

test/parallel/test-zlib-brotli-kmaxlength-rangeerror.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const assert = require('assert');
1111
// large Buffers.
1212
const buffer = require('buffer');
1313
const oldkMaxLength = buffer.kMaxLength;
14-
buffer.kMaxLength = 128;
14+
buffer.kMaxLength = 64;
1515
const zlib = require('zlib');
1616
buffer.kMaxLength = oldkMaxLength;
1717

test/parallel/test-zlib-kmaxlength-rangeerror.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const assert = require('assert');
1111
// large Buffers.
1212
const buffer = require('buffer');
1313
const oldkMaxLength = buffer.kMaxLength;
14-
buffer.kMaxLength = 128;
14+
buffer.kMaxLength = 64;
1515
const zlib = require('zlib');
1616
buffer.kMaxLength = oldkMaxLength;
1717

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const zlib = require('zlib');
5+
6+
const encoded = Buffer.from('G38A+CXCIrFAIAM=', 'base64');
7+
8+
// Async
9+
zlib.brotliDecompress(encoded, { maxOutputLength: 64 }, common.expectsError({
10+
code: 'ERR_BUFFER_TOO_LARGE',
11+
message: 'Cannot create a Buffer larger than 64 bytes'
12+
}));
13+
14+
// Sync
15+
assert.throws(function() {
16+
zlib.brotliDecompressSync(encoded, { maxOutputLength: 64 });
17+
}, RangeError);
18+
19+
// Async
20+
zlib.brotliDecompress(encoded, { maxOutputLength: 256 }, function(err) {
21+
assert.strictEqual(err, null);
22+
});
23+
24+
// Sync
25+
zlib.brotliDecompressSync(encoded, { maxOutputLength: 256 });

0 commit comments

Comments
 (0)