GeezerVison is showing github.com — zlib: expose rejectGarbageAfterEnd option · nodejs/node@90007a5
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/zlib.md +18Lines changed: 18 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -801,6 +801,9 @@ These advanced options are available for controlling decompression: | 801 | 801 | <!-- YAML | 802 | 802 | added: v0.11.1 | 803 | 803 | changes: | | 804 | + - version: REPLACEME | | 805 | + pr-url: https://github.com/nodejs/node/pull/64023 | | 806 | + description: The `rejectGarbageAfterEnd` option was added. | 804 | 807 | - version: | 805 | 808 | - v14.5.0 | 806 | 809 | - v12.19.0 | @@ -836,6 +839,10 @@ ignored by the decompression classes. | 836 | 839 | * `info` {boolean} (If `true`, returns an object with `buffer` and `engine`.) | 837 | 840 | * `maxOutputLength` {integer} Limits output size when using | 838 | 841 | [convenience methods][]. **Default:** [`buffer.kMaxLength`][] | | 842 | +* `rejectGarbageAfterEnd` {boolean} If `true`, decompression fails when | | 843 | + trailing input is detected after the end of the compressed stream. This | | 844 | + includes unreadable bytes and, when decompressing gzip, additional gzip | | 845 | + members following the first member. **Default:** `false` | 839 | 846 |
| 840 | 847 | See the [`deflateInit2` and `inflateInit2`][] documentation for more | 841 | 848 | information. | @@ -845,6 +852,9 @@ information. | 845 | 852 | <!-- YAML | 846 | 853 | added: v11.7.0 | 847 | 854 | changes: | | 855 | + - version: REPLACEME | | 856 | + pr-url: https://github.com/nodejs/node/pull/64023 | | 857 | + description: The `rejectGarbageAfterEnd` option was added. | 848 | 858 | - version: | 849 | 859 | - v14.5.0 | 850 | 860 | - v12.19.0 | @@ -863,6 +873,8 @@ Each Brotli-based class takes an `options` object. All options are optional. | 863 | 873 | * `maxOutputLength` {integer} Limits output size when using | 864 | 874 | [convenience methods][]. **Default:** [`buffer.kMaxLength`][] | 865 | 875 | * `info` {boolean} If `true`, returns an object with `buffer` and `engine`. **Default:** `false` | | 876 | +* `rejectGarbageAfterEnd` {boolean} If `true`, decompression fails when | | 877 | + input remains after the first complete compressed stream. **Default:** `false` | 866 | 878 |
| 867 | 879 | For example: | 868 | 880 |
| @@ -1086,6 +1098,10 @@ the inflate and deflate algorithms. | 1086 | 1098 | added: | 1087 | 1099 | - v23.8.0 | 1088 | 1100 | - v22.15.0 | | 1101 | +changes: | | 1102 | + - version: REPLACEME | | 1103 | + pr-url: https://github.com/nodejs/node/pull/64023 | | 1104 | + description: The `rejectGarbageAfterEnd` option was added. | 1089 | 1105 | --> | 1090 | 1106 |
| 1091 | 1107 | <!--type=misc--> | @@ -1102,6 +1118,8 @@ Each Zstd-based class takes an `options` object. All options are optional. | 1102 | 1118 | * `dictionary` {Buffer} Optional dictionary used to | 1103 | 1119 | improve compression efficiency when compressing or decompressing data that | 1104 | 1120 | shares common patterns with the dictionary. | | 1121 | +* `rejectGarbageAfterEnd` {boolean} If `true`, decompression fails when | | 1122 | + input remains after the first complete compressed stream. **Default:** `false` | 1105 | 1123 |
| 1106 | 1124 | For example: | 1107 | 1125 |
| |
Collapse file Copy file name to clipboardExpand all lines: lib/zlib.js +8Lines changed: 8 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -65,6 +65,7 @@ const { | 65 | 65 | const { owner_symbol } = require('internal/async_hooks').symbols; | 66 | 66 | const { | 67 | 67 | checkRangesOrGetDefault, | | 68 | + validateBoolean, | 68 | 69 | validateFunction, | 69 | 70 | validateUint32, | 70 | 71 | validateFiniteNumber, | @@ -246,6 +247,13 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) { | 246 | 247 | opts.maxOutputLength, 'options.maxOutputLength', | 247 | 248 | 1, kMaxLength, kMaxLength); | 248 | 249 |
| | 250 | + if (opts.rejectGarbageAfterEnd !== undefined) { | | 251 | + validateBoolean( | | 252 | + opts.rejectGarbageAfterEnd, | | 253 | + 'options.rejectGarbageAfterEnd', | | 254 | + ); | | 255 | + } | | 256 | + | 249 | 257 | if (opts.encoding || opts.objectMode || opts.writableObjectMode) { | 250 | 258 | opts = { ...opts }; | 251 | 259 | opts.encoding = null; | |
Collapse file Copy file name to clipboard +144Lines changed: 144 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +require('../common'); | | 4 | +const assert = require('assert'); | | 5 | +const test = require('node:test'); | | 6 | +const { finished } = require('stream/promises'); | | 7 | +const zlib = require('zlib'); | | 8 | + | | 9 | +const trailingJunkError = { | | 10 | + code: 'ERR_TRAILING_JUNK_AFTER_STREAM_END', | | 11 | + name: 'TypeError', | | 12 | +}; | | 13 | + | | 14 | +function callAsync(fn, input, options) { | | 15 | + return new Promise((resolve, reject) => { | | 16 | + fn(input, options, (err, result) => { | | 17 | + if (err) { | | 18 | + reject(err); | | 19 | + } else { | | 20 | + resolve(result); | | 21 | + } | | 22 | + }); | | 23 | + }); | | 24 | +} | | 25 | + | | 26 | +async function collect(stream, input) { | | 27 | + const chunks = []; | | 28 | + stream.on('data', (chunk) => chunks.push(chunk)); | | 29 | + stream.end(input); | | 30 | + await finished(stream); | | 31 | + return Buffer.concat(chunks); | | 32 | +} | | 33 | + | | 34 | +const cases = [ | | 35 | + { | | 36 | + label: 'inflate', | | 37 | + compress: zlib.deflateSync, | | 38 | + decompress: zlib.inflate, | | 39 | + decompressSync: zlib.inflateSync, | | 40 | + createDecompress: zlib.createInflate, | | 41 | + defaultOutput: 'a', | | 42 | + }, | | 43 | + { | | 44 | + label: 'inflateRaw', | | 45 | + compress: zlib.deflateRawSync, | | 46 | + decompress: zlib.inflateRaw, | | 47 | + decompressSync: zlib.inflateRawSync, | | 48 | + createDecompress: zlib.createInflateRaw, | | 49 | + defaultOutput: 'a', | | 50 | + }, | | 51 | + { | | 52 | + label: 'gunzip', | | 53 | + compress: zlib.gzipSync, | | 54 | + decompress: zlib.gunzip, | | 55 | + decompressSync: zlib.gunzipSync, | | 56 | + createDecompress: zlib.createGunzip, | | 57 | + defaultOutput: 'aa', | | 58 | + }, | | 59 | + { | | 60 | + label: 'unzip', | | 61 | + compress: zlib.gzipSync, | | 62 | + decompress: zlib.unzip, | | 63 | + decompressSync: zlib.unzipSync, | | 64 | + createDecompress: zlib.createUnzip, | | 65 | + defaultOutput: 'aa', | | 66 | + }, | | 67 | + { | | 68 | + label: 'brotli', | | 69 | + compress: zlib.brotliCompressSync, | | 70 | + decompress: zlib.brotliDecompress, | | 71 | + decompressSync: zlib.brotliDecompressSync, | | 72 | + createDecompress: zlib.createBrotliDecompress, | | 73 | + defaultOutput: 'a', | | 74 | + }, | | 75 | + { | | 76 | + label: 'zstd', | | 77 | + compress: zlib.zstdCompressSync, | | 78 | + decompress: zlib.zstdDecompress, | | 79 | + decompressSync: zlib.zstdDecompressSync, | | 80 | + createDecompress: zlib.createZstdDecompress, | | 81 | + defaultOutput: 'a', | | 82 | + }, | | 83 | +]; | | 84 | + | | 85 | +for (const { | | 86 | + label, | | 87 | + compress, | | 88 | + decompress, | | 89 | + decompressSync, | | 90 | + createDecompress, | | 91 | + defaultOutput, | | 92 | +} of cases) { | | 93 | + test(`rejectGarbageAfterEnd rejects trailing input for ${label}`, async () => { | | 94 | + const compressed = compress(Buffer.from('a')); | | 95 | + const withTrailingInput = Buffer.concat([compressed, compressed]); | | 96 | + | | 97 | + assert.strictEqual(decompressSync(withTrailingInput).toString(), defaultOutput); | | 98 | + assert.strictEqual( | | 99 | + (await callAsync(decompress, withTrailingInput)).toString(), | | 100 | + defaultOutput, | | 101 | + ); | | 102 | + assert.strictEqual( | | 103 | + (await collect(createDecompress(), withTrailingInput)).toString(), | | 104 | + defaultOutput, | | 105 | + ); | | 106 | + | | 107 | + assert.throws( | | 108 | + () => decompressSync(withTrailingInput, { rejectGarbageAfterEnd: true }), | | 109 | + trailingJunkError, | | 110 | + ); | | 111 | + await assert.rejects( | | 112 | + callAsync(decompress, withTrailingInput, { rejectGarbageAfterEnd: true }), | | 113 | + trailingJunkError, | | 114 | + ); | | 115 | + await assert.rejects( | | 116 | + collect( | | 117 | + createDecompress({ rejectGarbageAfterEnd: true }), | | 118 | + withTrailingInput, | | 119 | + ), | | 120 | + trailingJunkError, | | 121 | + ); | | 122 | + }); | | 123 | +} | | 124 | + | | 125 | +test('rejectGarbageAfterEnd must be a boolean', () => { | | 126 | + const compressed = zlib.deflateSync(Buffer.from('a')); | | 127 | + | | 128 | + for (const value of [1, 'true', null]) { | | 129 | + assert.throws( | | 130 | + () => zlib.inflateSync(compressed, { rejectGarbageAfterEnd: value }), | | 131 | + { | | 132 | + code: 'ERR_INVALID_ARG_TYPE', | | 133 | + name: 'TypeError', | | 134 | + }, | | 135 | + ); | | 136 | + assert.throws( | | 137 | + () => zlib.createInflate({ rejectGarbageAfterEnd: value }), | | 138 | + { | | 139 | + code: 'ERR_INVALID_ARG_TYPE', | | 140 | + name: 'TypeError', | | 141 | + }, | | 142 | + ); | | 143 | + } | | 144 | +}); |
|