Skip to content

Commit 81e4acc

Browse files
committed
zlib: add maxOutputLength option
Fixes: nodejs#27253
1 parent c15a27c commit 81e4acc

File tree

6 files changed

+53
-12
lines changed

6 files changed

+53
-12
lines changed

doc/api/zlib.md

+13
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,9 @@ These advanced options are available for controlling decompression:
484484
<!-- YAML
485485
added: v0.11.1
486486
changes:
487+
- version: REPLACEME
488+
pr-url: REPLACEME
489+
description: The `maxOutputLength` option is supported now.
487490
- version: v9.4.0
488491
pr-url: https://github.com/nodejs/node/pull/16042
489492
description: The `dictionary` option can be an `ArrayBuffer`.
@@ -512,13 +515,19 @@ ignored by the decompression classes.
512515
* `dictionary` {Buffer|TypedArray|DataView|ArrayBuffer} (deflate/inflate only,
513516
empty dictionary by default)
514517
* `info` {boolean} (If `true`, returns an object with `buffer` and `engine`.)
518+
* `maxOutputLength` {integer} Limits output size when using
519+
[convenience methods][]. **Default:** [`buffer.kMaxLength`][]
515520

516521
See the [`deflateInit2` and `inflateInit2`][] documentation for more
517522
information.
518523

519524
## Class: `BrotliOptions`
520525
<!-- YAML
521526
added: v11.7.0
527+
changes:
528+
- version: REPLACEME
529+
pr-url: REPLACEME
530+
description: The `maxOutputLength` option is supported now.
522531
-->
523532

524533
<!--type=misc-->
@@ -529,6 +538,8 @@ Each Brotli-based class takes an `options` object. All options are optional.
529538
* `finishFlush` {integer} **Default:** `zlib.constants.BROTLI_OPERATION_FINISH`
530539
* `chunkSize` {integer} **Default:** `16 * 1024`
531540
* `params` {Object} Key-value object containing indexed [Brotli parameters][].
541+
* `maxOutputLength` {integer} Limits output size when using
542+
[convenience methods][]. **Default:** [`buffer.kMaxLength`][]
532543

533544
For example:
534545

@@ -1140,6 +1151,7 @@ Decompress a chunk of data with [`Unzip`][].
11401151
[`BrotliCompress`]: #zlib_class_zlib_brotlicompress
11411152
[`BrotliDecompress`]: #zlib_class_zlib_brotlidecompress
11421153
[`Buffer`]: buffer.html#buffer_class_buffer
1154+
[`buffer.kMaxLength`]: buffer.html#buffer_buffer_kmaxlength
11431155
[`Content-Encoding`]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
11441156
[`DataView`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView
11451157
[`DeflateRaw`]: #zlib_class_zlib_deflateraw
@@ -1157,5 +1169,6 @@ Decompress a chunk of data with [`Unzip`][].
11571169
[Memory Usage Tuning]: #zlib_memory_usage_tuning
11581170
[RFC 7932]: https://www.rfc-editor.org/rfc/rfc7932.txt
11591171
[Streams API]: stream.md
1172+
[convenience methods]: #zlib_convenience_methods
11601173
[zlib documentation]: https://zlib.net/manual.html#Constants
11611174
[zlib.createGzip example]: #zlib_zlib

lib/internal/errors.js

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

49-
const { kMaxLength } = internalBinding('buffer');
50-
5149
const MainContextError = Error;
5250
const ErrorToString = Error.prototype.toString;
5351
const overrideStackTrace = new WeakMap();
@@ -747,7 +745,7 @@ E('ERR_BUFFER_OUT_OF_BOUNDS',
747745
return 'Attempt to access memory outside buffer bounds';
748746
}, RangeError);
749747
E('ERR_BUFFER_TOO_LARGE',
750-
`Cannot create a Buffer larger than 0x${kMaxLength.toString(16)} bytes`,
748+
'Cannot create a Buffer larger than %s bytes',
751749
RangeError);
752750
E('ERR_CANNOT_WATCH_SIGINT', 'Cannot watch for SIGINT signals', Error);
753751
E('ERR_CHILD_CLOSED_BEFORE_REPLY',

lib/zlib.js

+13-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;
@@ -276,6 +279,7 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
276279
this._finishFlushFlag = finishFlush;
277280
this._defaultFullFlushFlag = fullFlush;
278281
this._info = opts && opts.info;
282+
this._maxOutputLength = (opts && opts.maxOutputLength) || kMaxLength;
279283
}
280284
ObjectSetPrototypeOf(ZlibBase.prototype, Transform.prototype);
281285
ObjectSetPrototypeOf(ZlibBase, Transform);
@@ -450,6 +454,12 @@ function processChunkSync(self, chunk, flushFlag) {
450454
else
451455
buffers.push(out);
452456
nread += out.byteLength;
457+
458+
if (nread > self._maxOutputLength) {
459+
_close(self);
460+
throw new ERR_BUFFER_TOO_LARGE(self._maxOutputLength);
461+
}
462+
453463
} else {
454464
assert(have === 0, 'have should not go down');
455465
}
@@ -476,10 +486,6 @@ function processChunkSync(self, chunk, flushFlag) {
476486
self.bytesWritten = inputRead;
477487
_close(self);
478488

479-
if (nread >= kMaxLength) {
480-
throw new ERR_BUFFER_TOO_LARGE();
481-
}
482-
483489
if (nread === 0)
484490
return Buffer.alloc(0);
485491

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

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
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 }, function(err) {
10+
assert.ok(err instanceof RangeError);
11+
});
12+
13+
// Sync
14+
assert.throws(function() {
15+
zlib.brotliDecompressSync(encoded, { maxOutputLength: 64 });
16+
}, RangeError);
17+
18+
// Async
19+
zlib.brotliDecompress(encoded, { maxOutputLength: 256 }, function(err) {
20+
assert.strictEqual(err, null);
21+
});
22+
23+
// Sync
24+
zlib.brotliDecompressSync(encoded, { maxOutputLength: 256 });

0 commit comments

Comments
 (0)