| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d5f01bb commit 98be430
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -276,6 +276,7 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) { | |||
| 276 | 276 | this._defaultFlushFlag = flush; | |
| 277 | 277 | this._finishFlushFlag = finishFlush; | |
| 278 | 278 | this._defaultFullFlushFlag = fullFlush; | |
| 279 | + this._flushBoundIdx = flushBoundIdx; | ||
| 279 | 280 | this._info = opts?.info; | |
| 280 | 281 | this._maxOutputLength = maxOutputLength; | |
| 281 | 282 | ||
@@ -357,6 +358,18 @@ ZlibBase.prototype.flush = function(kind, callback) { | |||
| 357 | 358 | kind = this._defaultFullFlushFlag; | |
| 358 | 359 | } | |
| 359 | 360 | ||
| 361 | + // Reject kinds outside the brotli operation range. Otherwise the fake-chunk | ||
| 362 | + // path forwards an unsupported flush flag to the native encoder/decoder, | ||
| 363 | + // which spins at 100% CPU without making progress (see #63701). | ||
| 364 | + if (this._flushBoundIdx === FLUSH_BOUND_IDX_BROTLI) { | ||
| 365 | + const min = FLUSH_BOUND[FLUSH_BOUND_IDX_BROTLI][0]; | ||
| 366 | + const max = FLUSH_BOUND[FLUSH_BOUND_IDX_BROTLI][1]; | ||
| 367 | + if (typeof kind !== 'number' || NumberIsNaN(kind) || | ||
| 368 | + kind < min || kind > max) { | ||
| 369 | + throw new ERR_OUT_OF_RANGE('kind', `>= ${min} and <= ${max}`, kind); | ||
| 370 | + } | ||
| 371 | + } | ||
| 372 | + | ||
| 360 | 373 | if (this.writableFinished) { | |
| 361 | 374 | if (callback) | |
| 362 | 375 | process.nextTick(callback); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,69 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + // Regression test for https://github.com/nodejs/node/issues/63701 | ||
| 3 | + // BrotliCompress/BrotliDecompress.flush(kind) used to spin at 100% CPU when | ||
| 4 | + // kind was outside the brotli operation range (0..3) — e.g. Z_FINISH (4) or | ||
| 5 | + // Z_BLOCK (5). It must now throw ERR_OUT_OF_RANGE before reaching the native | ||
| 6 | + // layer. | ||
| 7 | + | ||
| 8 | + require('../common'); | ||
| 9 | + const assert = require('assert'); | ||
| 10 | + const zlib = require('zlib'); | ||
| 11 | + | ||
| 12 | + const { | ||
| 13 | + BROTLI_OPERATION_PROCESS, | ||
| 14 | + BROTLI_OPERATION_FLUSH, | ||
| 15 | + BROTLI_OPERATION_FINISH, | ||
| 16 | + BROTLI_OPERATION_EMIT_METADATA, | ||
| 17 | + Z_BLOCK, | ||
| 18 | + Z_FINISH, | ||
| 19 | + } = zlib.constants; | ||
| 20 | + | ||
| 21 | + // Brotli operations 0..3 are valid and must not throw synchronously from | ||
| 22 | + // flush(). Some operations (e.g. FINISH on a decoder with no input) emit a | ||
| 23 | + // Z_BUF_ERROR asynchronously — that's expected and unrelated to the input | ||
| 24 | + // validation under test, so we attach a noop error handler. | ||
| 25 | + for (const validKind of [ | ||
| 26 | + BROTLI_OPERATION_PROCESS, | ||
| 27 | + BROTLI_OPERATION_FLUSH, | ||
| 28 | + BROTLI_OPERATION_FINISH, | ||
| 29 | + BROTLI_OPERATION_EMIT_METADATA, | ||
| 30 | + ]) { | ||
| 31 | + const c = zlib.createBrotliCompress(); | ||
| 32 | + c.on('error', () => {}); | ||
| 33 | + c.flush(validKind); | ||
| 34 | + | ||
| 35 | + const d = zlib.createBrotliDecompress(); | ||
| 36 | + d.on('error', () => {}); | ||
| 37 | + d.flush(validKind); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + // Values outside [0, 3] must throw ERR_OUT_OF_RANGE for both compress and | ||
| 41 | + // decompress streams. Z_FINISH (4) and Z_BLOCK (5) previously hung at 100% CPU. | ||
| 42 | + const outOfRange = [-1, Z_FINISH, Z_BLOCK, 6, 7, 100]; | ||
| 43 | + | ||
| 44 | + for (const factory of [zlib.createBrotliCompress, zlib.createBrotliDecompress]) { | ||
| 45 | + for (const kind of outOfRange) { | ||
| 46 | + assert.throws( | ||
| 47 | + () => factory().flush(kind), | ||
| 48 | + { code: 'ERR_OUT_OF_RANGE', name: 'RangeError' }, | ||
| 49 | + ); | ||
| 50 | + } | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + // Non-number kinds must also throw, instead of silently triggering the | ||
| 54 | + // fake-chunk path with an undefined buffer. | ||
| 55 | + for (const factory of [zlib.createBrotliCompress, zlib.createBrotliDecompress]) { | ||
| 56 | + for (const kind of ['foobar', null, {}, NaN]) { | ||
| 57 | + assert.throws( | ||
| 58 | + () => factory().flush(kind), | ||
| 59 | + { code: 'ERR_OUT_OF_RANGE' }, | ||
| 60 | + ); | ||
| 61 | + } | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + // flush() with no arguments (or with only a callback) must still work — | ||
| 65 | + // it uses the stream's _defaultFullFlushFlag, which is a valid brotli op. | ||
| 66 | + zlib.createBrotliCompress().flush(); | ||
| 67 | + zlib.createBrotliCompress().flush(() => {}); | ||
| 68 | + zlib.createBrotliDecompress().flush(); | ||
| 69 | + zlib.createBrotliDecompress().flush(() => {}); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments