From 4179a6ee78be0327ef22a1ebd76dd1be95d7871a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Sat, 11 Jul 2026 14:36:33 +0200 Subject: [PATCH 1/8] AVRO-4287: [javascript] Enforce a maximum decompressed block size When reading a data file, each block is decompressed according to the file's codec. A block with a very high compression ratio (or a malformed block) could expand to far more memory than its compressed size. Bound the decompressed size of each block: the built-in deflate codec passes the limit to zlib (via maxOutputLength) so the allocation itself is capped, and every codec's output is additionally checked as a safeguard. Mirrors the Java SDK's decompression limit (AVRO-4247). The limit defaults to 200 MiB and can be set per decoder via the maxDecompressLength option or globally with the AVRO_MAX_DECOMPRESS_LENGTH environment variable. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/js/lib/files.js | 53 ++++++++++++++++++++++++++++++++++++-- lang/js/test/test_files.js | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/lang/js/lib/files.js b/lang/js/lib/files.js index 6d63cb45087..eaef28ea65d 100644 --- a/lang/js/lib/files.js +++ b/lang/js/lib/files.js @@ -58,6 +58,25 @@ var LONG_TYPE = schemas.createType('long'); // First 4 bytes of an Avro object container file. var MAGIC_BYTES = Buffer.from('Obj\x01'); +// Default upper bound, in bytes, on the size a single data-file block may +// decompress to. A block with a very high compression ratio (or a malformed +// block) can otherwise expand to far more memory than its compressed size. +// Mirrors the Java SDK's decompression limit (AVRO-4247). Overridable per +// decoder via the `maxDecompressLength` option, or globally with the +// `AVRO_MAX_DECOMPRESS_LENGTH` environment variable. +var DEFAULT_MAX_DECOMPRESS_LENGTH = 200 * 1024 * 1024; // 200 MiB + +function getDefaultMaxDecompressLength() { + var value = process.env.AVRO_MAX_DECOMPRESS_LENGTH; + if (value !== undefined) { + var parsed = parseInt(value, 10); + if (!isNaN(parsed) && parsed > 0) { + return parsed; + } + } + return DEFAULT_MAX_DECOMPRESS_LENGTH; +} + // Convenience. var f = util.format; var Tap = utils.Tap; @@ -145,6 +164,8 @@ function BlockDecoder(opts) { this._type = null; this._codecs = opts.codecs; this._parseOpts = opts.parseOpts || {}; + this._maxDecompressLength = opts.maxDecompressLength > 0 ? + opts.maxDecompressLength : getDefaultMaxDecompressLength(); this._tap = new Tap(Buffer.alloc(0)); this._blockTap = new Tap(Buffer.alloc(0)); this._syncMarker = null; @@ -187,12 +208,40 @@ BlockDecoder.prototype._decodeHeader = function () { } var codec = (header.meta['avro.codec'] || 'null').toString(); - this._decompress = (this._codecs || BlockDecoder.getDefaultCodecs())[codec]; - if (!this._decompress) { + var decompress = (this._codecs || BlockDecoder.getDefaultCodecs())[codec]; + if (!decompress) { this.emit('error', new Error(f('unknown codec: %s', codec))); return; } + // Bound the decompressed size of each block to guard against a block with a + // very high compression ratio (or a malformed block) expanding to far more + // memory than its compressed size. For the built-in deflate codec the limit + // is passed to zlib so the allocation itself is capped; every codec's output + // is additionally checked as a safeguard. + var maxLength = this._maxDecompressLength; + if (!this._codecs && codec === 'deflate') { + decompress = function (buf, cb) { + zlib.inflateRaw(buf, {maxOutputLength: maxLength}, cb); + }; + } + this._decompress = function (buf, cb) { + decompress(buf, function (err, data) { + if (err) { + cb(err); + return; + } + if (data && data.length > maxLength) { + cb(new Error(f( + 'decompressed block size exceeds the maximum allowed of %d bytes', + maxLength + ))); + return; + } + cb(null, data); + }); + }; + try { var schema = JSON.parse(header.meta['avro.schema'].toString()); this._type = parse(schema, this._parseOpts); diff --git a/lang/js/test/test_files.js b/lang/js/test/test_files.js index 43d5df60162..206116b91df 100644 --- a/lang/js/test/test_files.js +++ b/lang/js/test/test_files.js @@ -531,6 +531,52 @@ describe('files', function () { }); }); + it('rejects a block that decompresses beyond the limit', function (cb) { + // A large, highly compressible payload compresses to a tiny block but + // would decompress to far more than the configured limit. + var t = createType('string'); + var big = new Array(100001).join('a'); // 100000 bytes when decompressed + var encoder = new streams.BlockEncoder(t, {codec: 'deflate'}); + var decoder = new streams.BlockDecoder({maxDecompressLength: 1024}) + .on('data', function () {}) + .on('error', function () { cb(); }); + encoder.pipe(decoder); + encoder.end(big); + }); + + it('decompresses a block within the limit', function (cb) { + var t = createType('string'); + var payload = 'hello world'; + var out = []; + var encoder = new streams.BlockEncoder(t, {codec: 'deflate'}); + var decoder = new streams.BlockDecoder({maxDecompressLength: 1024}) + .on('data', function (s) { out.push(s); }) + .on('end', function () { + assert.deepEqual(out, [payload]); + cb(); + }); + encoder.pipe(decoder); + encoder.end(payload); + }); + + it('enforces the limit for custom codecs', function (cb) { + // A custom codec that yields more than the limit is rejected by the + // size safeguard applied to every codec. + var t = createType('int'); + var codecs = { + 'null': function (data, cb) { cb(null, Buffer.alloc(4096)); } + }; + var encoder = new streams.BlockEncoder(t, {codec: 'null'}); + var decoder = new streams.BlockDecoder({ + codecs: codecs, + maxDecompressLength: 1024 + }) + .on('data', function () {}) + .on('error', function () { cb(); }); + encoder.pipe(decoder); + encoder.end(1); + }); + }); it('createFileDecoder', function (cb) { From a4a66808c704c6b70abfe33d4c3098a8ba61f042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Sat, 11 Jul 2026 17:38:02 +0200 Subject: [PATCH 2/8] AVRO-4287: [javascript] Address review: cap reused inflater; fix pending on error - Apply zlib's maxOutputLength cap whenever the resolved codec is zlib.inflateRaw, including a custom codecs map that reuses it (previously the cap was only applied for the built-in default, so custom maps fell back to the too-late post-decompress check). - Decrement _pending in the block callback's error path so a failed decompression (e.g. the size safeguard firing) cannot leave the stream waiting at finish. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/js/lib/files.js | 14 +++++++++----- lang/js/test/test_files.js | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/lang/js/lib/files.js b/lang/js/lib/files.js index eaef28ea65d..e163cc8d212 100644 --- a/lang/js/lib/files.js +++ b/lang/js/lib/files.js @@ -216,11 +216,12 @@ BlockDecoder.prototype._decodeHeader = function () { // Bound the decompressed size of each block to guard against a block with a // very high compression ratio (or a malformed block) expanding to far more - // memory than its compressed size. For the built-in deflate codec the limit - // is passed to zlib so the allocation itself is capped; every codec's output - // is additionally checked as a safeguard. + // memory than its compressed size. Whenever the resolved codec is Node's raw + // inflater -- the built-in default or a custom codecs map that reuses it -- + // the limit is passed to zlib so the allocation itself is capped; every + // codec's output is additionally checked as a safeguard. var maxLength = this._maxDecompressLength; - if (!this._codecs && codec === 'deflate') { + if (decompress === zlib.inflateRaw) { decompress = function (buf, cb) { zlib.inflateRaw(buf, {maxOutputLength: maxLength}, cb); }; @@ -296,11 +297,14 @@ BlockDecoder.prototype._createBlockCallback = function () { this._pending++; return function (err, data) { + // Always account for this block's completion, even on error, so a failed + // decompression (e.g. the size safeguard firing) cannot leave `_pending` + // permanently above zero and hang the stream at finish. + self._pending--; if (err) { self.emit('error', err); return; } - self._pending--; self._queue.push(new BlockData(index, data)); if (self._needPush) { self._needPush = false; diff --git a/lang/js/test/test_files.js b/lang/js/test/test_files.js index 206116b91df..ecfe8e07546 100644 --- a/lang/js/test/test_files.js +++ b/lang/js/test/test_files.js @@ -577,6 +577,23 @@ describe('files', function () { encoder.end(1); }); + it('caps a custom deflate codec that reuses zlib.inflateRaw', function (cb) { + // A custom codecs map that reuses the built-in raw inflater must still get + // the zlib maxOutputLength cap (not just the post-decompress check). + var zlib = require('zlib'); + var t = createType('string'); + var big = new Array(100001).join('a'); // 100000 bytes when decompressed + var encoder = new streams.BlockEncoder(t, {codec: 'deflate'}); + var decoder = new streams.BlockDecoder({ + codecs: {deflate: zlib.inflateRaw}, + maxDecompressLength: 1024 + }) + .on('data', function () {}) + .on('error', function () { cb(); }); + encoder.pipe(decoder); + encoder.end(big); + }); + }); it('createFileDecoder', function (cb) { From d1370e7088e538fdd9e28f3a715aeef6a4187f26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Sat, 11 Jul 2026 18:51:53 +0200 Subject: [PATCH 3/8] AVRO-4287: [javascript] End readable side on late error; assert error specifics - In the block callback's error path, if the writable side already finished and this was the last pending block, end the readable side (push null) so a consumer waiting on the queue is not left hanging after the error. - The limit tests now assert the specific error: zlib's ERR_BUFFER_TOO_LARGE for the deflate maxOutputLength cap (built-in and custom-codec-reusing- inflateRaw), and the 'decompressed block size exceeds' message for the generic custom-codec safeguard. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/js/lib/files.js | 7 +++++++ lang/js/test/test_files.js | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lang/js/lib/files.js b/lang/js/lib/files.js index e163cc8d212..e3a1db5c789 100644 --- a/lang/js/lib/files.js +++ b/lang/js/lib/files.js @@ -303,6 +303,13 @@ BlockDecoder.prototype._createBlockCallback = function () { self._pending--; if (err) { self.emit('error', err); + // If the writable side already finished and this was the last pending + // block, end the readable side too so a consumer waiting on the queue is + // not left hanging after the error. + if (self._needPush && self._finished && !self._pending) { + self._needPush = false; + self.push(null); + } return; } self._queue.push(new BlockData(index, data)); diff --git a/lang/js/test/test_files.js b/lang/js/test/test_files.js index ecfe8e07546..b427021d3bf 100644 --- a/lang/js/test/test_files.js +++ b/lang/js/test/test_files.js @@ -539,7 +539,11 @@ describe('files', function () { var encoder = new streams.BlockEncoder(t, {codec: 'deflate'}); var decoder = new streams.BlockDecoder({maxDecompressLength: 1024}) .on('data', function () {}) - .on('error', function () { cb(); }); + .on('error', function (err) { + // zlib's maxOutputLength cap fires, so the allocation itself is bounded. + assert.equal(err.code, 'ERR_BUFFER_TOO_LARGE'); + cb(); + }); encoder.pipe(decoder); encoder.end(big); }); @@ -572,7 +576,11 @@ describe('files', function () { maxDecompressLength: 1024 }) .on('data', function () {}) - .on('error', function () { cb(); }); + .on('error', function (err) { + // The generic post-decompress size safeguard fires for custom codecs. + assert(/decompressed block size exceeds/.test(err.message)); + cb(); + }); encoder.pipe(decoder); encoder.end(1); }); @@ -589,7 +597,10 @@ describe('files', function () { maxDecompressLength: 1024 }) .on('data', function () {}) - .on('error', function () { cb(); }); + .on('error', function (err) { + assert.equal(err.code, 'ERR_BUFFER_TOO_LARGE'); + cb(); + }); encoder.pipe(decoder); encoder.end(big); }); From 3cba1088d5435c53fd95065aaafa61a92887324d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Sat, 11 Jul 2026 19:27:40 +0200 Subject: [PATCH 4/8] AVRO-4287: [javascript] Normalize and clamp the decompression limit Both AVRO_MAX_DECOMPRESS_LENGTH and the maxDecompressLength option are now run through a single normalizer that yields a positive, finite integer bounded by the runtime's maximum buffer length. This prevents a very large, non-integer, or non-finite value from reaching zlib's maxOutputLength and throwing synchronously. Added a test that an out-of-range limit is clamped rather than crashing. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/js/lib/files.js | 38 ++++++++++++++++++++++++++++---------- lang/js/test/test_files.js | 18 ++++++++++++++++++ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/lang/js/lib/files.js b/lang/js/lib/files.js index e3a1db5c789..6a5d87587aa 100644 --- a/lang/js/lib/files.js +++ b/lang/js/lib/files.js @@ -24,6 +24,7 @@ var protocols = require('./protocols'), schemas = require('./schemas'), utils = require('./utils'), + buffer = require('buffer'), fs = require('fs'), stream = require('stream'), util = require('util'), @@ -66,15 +67,31 @@ var MAGIC_BYTES = Buffer.from('Obj\x01'); // `AVRO_MAX_DECOMPRESS_LENGTH` environment variable. var DEFAULT_MAX_DECOMPRESS_LENGTH = 200 * 1024 * 1024; // 200 MiB -function getDefaultMaxDecompressLength() { - var value = process.env.AVRO_MAX_DECOMPRESS_LENGTH; - if (value !== undefined) { - var parsed = parseInt(value, 10); - if (!isNaN(parsed) && parsed > 0) { - return parsed; - } +// Largest value safe to pass as zlib's `maxOutputLength`. A value above the +// runtime's maximum buffer length (or a non-finite / non-integer one) can make +// `zlib.inflateRaw` throw synchronously, so every configured limit is +// normalized to a positive integer bounded by this cap before use. +var MAX_DECOMPRESS_LENGTH_CAP = + (buffer.constants && buffer.constants.MAX_LENGTH) || 0x7fffffff; + +// Normalize a user- or environment-supplied limit to a positive, finite +// integer bounded by MAX_DECOMPRESS_LENGTH_CAP. Returns undefined when the +// input is missing or invalid, so callers can fall back to a default. +function normalizeMaxDecompressLength(value) { + var parsed = typeof value === 'number' ? value : parseInt(value, 10); + if (!isFinite(parsed) || parsed <= 0) { + return undefined; + } + parsed = Math.floor(parsed); + if (parsed > MAX_DECOMPRESS_LENGTH_CAP) { + return MAX_DECOMPRESS_LENGTH_CAP; } - return DEFAULT_MAX_DECOMPRESS_LENGTH; + return parsed; +} + +function getDefaultMaxDecompressLength() { + var normalized = normalizeMaxDecompressLength(process.env.AVRO_MAX_DECOMPRESS_LENGTH); + return normalized === undefined ? DEFAULT_MAX_DECOMPRESS_LENGTH : normalized; } // Convenience. @@ -164,8 +181,9 @@ function BlockDecoder(opts) { this._type = null; this._codecs = opts.codecs; this._parseOpts = opts.parseOpts || {}; - this._maxDecompressLength = opts.maxDecompressLength > 0 ? - opts.maxDecompressLength : getDefaultMaxDecompressLength(); + var normalizedMax = normalizeMaxDecompressLength(opts.maxDecompressLength); + this._maxDecompressLength = normalizedMax === undefined ? + getDefaultMaxDecompressLength() : normalizedMax; this._tap = new Tap(Buffer.alloc(0)); this._blockTap = new Tap(Buffer.alloc(0)); this._syncMarker = null; diff --git a/lang/js/test/test_files.js b/lang/js/test/test_files.js index b427021d3bf..69fefb2d713 100644 --- a/lang/js/test/test_files.js +++ b/lang/js/test/test_files.js @@ -563,6 +563,24 @@ describe('files', function () { encoder.end(payload); }); + it('clamps an out-of-range limit instead of throwing', function (cb) { + // A limit larger than the runtime's maximum buffer length (or otherwise + // out of range) must be normalized so it does not make zlib throw + // synchronously; a normal block still decodes. + var t = createType('string'); + var payload = 'hello world'; + var out = []; + var encoder = new streams.BlockEncoder(t, {codec: 'deflate'}); + var decoder = new streams.BlockDecoder({maxDecompressLength: Infinity}) + .on('data', function (s) { out.push(s); }) + .on('end', function () { + assert.deepEqual(out, [payload]); + cb(); + }); + encoder.pipe(decoder); + encoder.end(payload); + }); + it('enforces the limit for custom codecs', function (cb) { // A custom codec that yields more than the limit is rejected by the // size safeguard applied to every codec. From e2bb138cd0c03c72156e8ccda1eb06b44f04a332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Mon, 13 Jul 2026 01:37:35 +0200 Subject: [PATCH 5/8] AVRO-4287: [js] Rename out-of-range limit test to reflect fallback behavior normalizeMaxDecompressLength(Infinity) returns undefined (non-finite), so the decoder falls back to the default limit rather than clamping to MAX_DECOMPRESS_LENGTH_CAP. Rename the test (and reword its comment) to describe the normalization/fallback it actually exercises. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/js/test/test_files.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lang/js/test/test_files.js b/lang/js/test/test_files.js index 69fefb2d713..9ed57ed4a9e 100644 --- a/lang/js/test/test_files.js +++ b/lang/js/test/test_files.js @@ -563,10 +563,10 @@ describe('files', function () { encoder.end(payload); }); - it('clamps an out-of-range limit instead of throwing', function (cb) { - // A limit larger than the runtime's maximum buffer length (or otherwise - // out of range) must be normalized so it does not make zlib throw - // synchronously; a normal block still decodes. + it('falls back to the default limit for an out-of-range value', function (cb) { + // A non-finite/out-of-range limit is not usable, so normalizeMaxDecompressLength + // returns undefined and the decoder falls back to its default limit rather + // than making zlib throw synchronously; a normal block still decodes. var t = createType('string'); var payload = 'hello world'; var out = []; From 09b9b19fb77b4c7faba13fdb86a523f7c4d00352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Mon, 13 Jul 2026 01:49:12 +0200 Subject: [PATCH 6/8] AVRO-4287: [js] Clear AVRO_MAX_DECOMPRESS_LENGTH in the fallback test The out-of-range fallback test asserts the decoder uses the library default, but the default can be overridden by AVRO_MAX_DECOMPRESS_LENGTH. Clear the env var for the duration of the test (restoring it afterward) so it is deterministic regardless of the runner environment. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/js/test/test_files.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lang/js/test/test_files.js b/lang/js/test/test_files.js index 9ed57ed4a9e..8e07a239eed 100644 --- a/lang/js/test/test_files.js +++ b/lang/js/test/test_files.js @@ -567,6 +567,16 @@ describe('files', function () { // A non-finite/out-of-range limit is not usable, so normalizeMaxDecompressLength // returns undefined and the decoder falls back to its default limit rather // than making zlib throw synchronously; a normal block still decodes. + // Clear the env override so the fallback default is deterministic. + var savedEnv = process.env.AVRO_MAX_DECOMPRESS_LENGTH; + delete process.env.AVRO_MAX_DECOMPRESS_LENGTH; + var restore = function () { + if (savedEnv === undefined) { + delete process.env.AVRO_MAX_DECOMPRESS_LENGTH; + } else { + process.env.AVRO_MAX_DECOMPRESS_LENGTH = savedEnv; + } + }; var t = createType('string'); var payload = 'hello world'; var out = []; @@ -574,6 +584,7 @@ describe('files', function () { var decoder = new streams.BlockDecoder({maxDecompressLength: Infinity}) .on('data', function (s) { out.push(s); }) .on('end', function () { + restore(); assert.deepEqual(out, [payload]); cb(); }); From 1592907a61ffc9ddfb6634aaaa54e6ae5123bc9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Mon, 13 Jul 2026 02:11:54 +0200 Subject: [PATCH 7/8] AVRO-4287: [js] Restore env on the error path in the fallback test The fallback test only restored AVRO_MAX_DECOMPRESS_LENGTH on the 'end' event. If the decoder emitted 'error', the env var stayed modified and could cascade into later tests. Add an 'error' handler that restores it (and fails the test), so the env is restored on both success and failure. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/js/test/test_files.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lang/js/test/test_files.js b/lang/js/test/test_files.js index 8e07a239eed..e7f6e4e9ca1 100644 --- a/lang/js/test/test_files.js +++ b/lang/js/test/test_files.js @@ -583,6 +583,7 @@ describe('files', function () { var encoder = new streams.BlockEncoder(t, {codec: 'deflate'}); var decoder = new streams.BlockDecoder({maxDecompressLength: Infinity}) .on('data', function (s) { out.push(s); }) + .on('error', function (err) { restore(); cb(err); }) .on('end', function () { restore(); assert.deepEqual(out, [payload]); From e110532cc37297252327362ff493d5abcd3e14af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Mon, 13 Jul 2026 06:47:15 +0200 Subject: [PATCH 8/8] AVRO-4287: [javascript] Add normalizeMaxDecompressLength regression tests Export normalizeMaxDecompressLength and MAX_DECOMPRESS_LENGTH_CAP for tests and add unit coverage for the normalization paths, including the previously untested "finite but > cap" case (clamps to MAX_DECOMPRESS_LENGTH_CAP), flooring, string parsing, and the invalid/out-of-range fallbacks to undefined. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/js/lib/files.js | 2 ++ lang/js/test/test_files.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/lang/js/lib/files.js b/lang/js/lib/files.js index 6a5d87587aa..65f58d6ea1d 100644 --- a/lang/js/lib/files.js +++ b/lang/js/lib/files.js @@ -741,6 +741,8 @@ function loadSchema(schema) { module.exports = { HEADER_TYPE: HEADER_TYPE, // For tests. MAGIC_BYTES: MAGIC_BYTES, // Idem. + MAX_DECOMPRESS_LENGTH_CAP: MAX_DECOMPRESS_LENGTH_CAP, // Idem. + normalizeMaxDecompressLength: normalizeMaxDecompressLength, // Idem. parse: parse, createFileDecoder: createFileDecoder, createFileEncoder: createFileEncoder, diff --git a/lang/js/test/test_files.js b/lang/js/test/test_files.js index e7f6e4e9ca1..a39e2318ab3 100644 --- a/lang/js/test/test_files.js +++ b/lang/js/test/test_files.js @@ -615,6 +615,24 @@ describe('files', function () { encoder.end(1); }); + it('normalizes decompress limits', function () { + var cap = files.MAX_DECOMPRESS_LENGTH_CAP; + var normalize = files.normalizeMaxDecompressLength; + // A finite value above the cap is clamped to the cap (the "> cap" path). + assert.equal(normalize(cap + 1), cap); + assert.equal(normalize(Number.MAX_VALUE), cap); + // A finite value at/below the cap is floored and kept. + assert.equal(normalize(1024), 1024); + assert.equal(normalize(1024.9), 1024); + assert.equal(normalize('2048'), 2048); + // Missing / invalid / out-of-range values fall back to undefined. + assert.equal(normalize(undefined), undefined); + assert.equal(normalize(Infinity), undefined); + assert.equal(normalize(0), undefined); + assert.equal(normalize(-1), undefined); + assert.equal(normalize('nope'), undefined); + }); + it('caps a custom deflate codec that reuses zlib.inflateRaw', function (cb) { // A custom codecs map that reuses the built-in raw inflater must still get // the zlib maxOutputLength cap (not just the post-decompress check).