GeezerVison is showing github.com — stream: making DecompressionStream spec compilent for trailing junk · nodejs/node@e8a07f2
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/errors.md +9Lines changed: 9 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -3012,6 +3012,15 @@ category. | 3012 | 3012 | The `node:trace_events` module could not be loaded because Node.js was compiled | 3013 | 3013 | with the `--without-v8-platform` flag. | 3014 | 3014 |
| | 3015 | +<a id="ERR_TRAILING_JUNK_AFTER_STREAM_END"></a> | | 3016 | + | | 3017 | +### `ERR_TRAILING_JUNK_AFTER_STREAM_END` | | 3018 | + | | 3019 | +Trailing junk found after the end of the compressed stream. | | 3020 | +This error is thrown when extra, unexpected data is detected | | 3021 | +after the end of a compressed stream (for example, in zlib | | 3022 | +or gzip decompression). | | 3023 | + | 3015 | 3024 | <a id="ERR_TRANSFORM_ALREADY_TRANSFORMING"></a> | 3016 | 3025 |
| 3017 | 3026 | ### `ERR_TRANSFORM_ALREADY_TRANSFORMING` | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/errors.js +2Lines changed: 2 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1803,6 +1803,8 @@ E('ERR_TRACE_EVENTS_CATEGORY_REQUIRED', | 1803 | 1803 | 'At least one category is required', TypeError); | 1804 | 1804 | E('ERR_TRACE_EVENTS_UNAVAILABLE', 'Trace events are unavailable', Error); | 1805 | 1805 |
| | 1806 | +E('ERR_TRAILING_JUNK_AFTER_STREAM_END', 'Trailing junk found after the end of the compressed stream', TypeError); | | 1807 | + | 1806 | 1808 | // This should probably be a `RangeError`. | 1807 | 1809 | E('ERR_TTY_INIT_FAILED', 'TTY initialization failed', SystemError); | 1808 | 1810 | E('ERR_UNAVAILABLE_DURING_EXIT', 'Cannot call function in process exit ' + | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/webstreams/compression.js +14-2Lines changed: 14 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -99,16 +99,28 @@ class DecompressionStream { | 99 | 99 | }); | 100 | 100 | switch (format) { | 101 | 101 | case 'deflate': | 102 | | - this.#handle = lazyZlib().createInflate(); | | 102 | + this.#handle = lazyZlib().createInflate({ | | 103 | + rejectGarbageAfterEnd: true, | | 104 | + }); | 103 | 105 | break; | 104 | 106 | case 'deflate-raw': | 105 | 107 | this.#handle = lazyZlib().createInflateRaw(); | 106 | 108 | break; | 107 | 109 | case 'gzip': | 108 | | - this.#handle = lazyZlib().createGunzip(); | | 110 | + this.#handle = lazyZlib().createGunzip({ | | 111 | + rejectGarbageAfterEnd: true, | | 112 | + }); | 109 | 113 | break; | 110 | 114 | } | 111 | 115 | this.#transform = newReadableWritablePairFromDuplex(this.#handle); | | 116 | + | | 117 | + this.#handle.on('error', (err) => { | | 118 | + if (this.#transform?.writable && | | 119 | + !this.#transform.writable.locked && | | 120 | + typeof this.#transform.writable.abort === 'function') { | | 121 | + this.#transform.writable.abort(err); | | 122 | + } | | 123 | + }); | 112 | 124 | } | 113 | 125 |
| 114 | 126 | /** | |
Collapse file Copy file name to clipboardExpand all lines: lib/zlib.js +12Lines changed: 12 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -42,6 +42,7 @@ const { | 42 | 42 | ERR_BUFFER_TOO_LARGE, | 43 | 43 | ERR_INVALID_ARG_TYPE, | 44 | 44 | ERR_OUT_OF_RANGE, | | 45 | + ERR_TRAILING_JUNK_AFTER_STREAM_END, | 45 | 46 | ERR_ZSTD_INVALID_PARAM, | 46 | 47 | }, | 47 | 48 | genericNodeError, | @@ -266,6 +267,8 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) { | 266 | 267 | this._defaultFullFlushFlag = fullFlush; | 267 | 268 | this._info = opts?.info; | 268 | 269 | this._maxOutputLength = maxOutputLength; | | 270 | + | | 271 | + this._rejectGarbageAfterEnd = opts?.rejectGarbageAfterEnd === true; | 269 | 272 | } | 270 | 273 | ObjectSetPrototypeOf(ZlibBase.prototype, Transform.prototype); | 271 | 274 | ObjectSetPrototypeOf(ZlibBase, Transform); | @@ -570,6 +573,14 @@ function processCallback() { | 570 | 573 | // stream has ended early. | 571 | 574 | // This applies to streams where we don't check data past the end of | 572 | 575 | // what was consumed; that is, everything except Gunzip/Unzip. | | 576 | + | | 577 | + if (self._rejectGarbageAfterEnd) { | | 578 | + const err = new ERR_TRAILING_JUNK_AFTER_STREAM_END(); | | 579 | + self.destroy(err); | | 580 | + this.cb(err); | | 581 | + return; | | 582 | + } | | 583 | + | 573 | 584 | self.push(null); | 574 | 585 | } | 575 | 586 |
| @@ -662,6 +673,7 @@ function Zlib(opts, mode) { | 662 | 673 |
| 663 | 674 | this._level = level; | 664 | 675 | this._strategy = strategy; | | 676 | + this._mode = mode; | 665 | 677 | } | 666 | 678 | ObjectSetPrototypeOf(Zlib.prototype, ZlibBase.prototype); | 667 | 679 | ObjectSetPrototypeOf(Zlib, ZlibBase); | |
Collapse file Copy file name to clipboard +46Lines changed: 46 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | +require('../common'); | | 3 | +const assert = require('assert').strict; | | 4 | +const test = require('node:test'); | | 5 | +const { DecompressionStream } = require('stream/web'); | | 6 | + | | 7 | +async function expectTypeError(promise) { | | 8 | + let threw = false; | | 9 | + try { | | 10 | + await promise; | | 11 | + } catch (err) { | | 12 | + threw = true; | | 13 | + assert(err instanceof TypeError, `Expected TypeError, got ${err}`); | | 14 | + } | | 15 | + assert(threw, 'Expected promise to reject'); | | 16 | +} | | 17 | + | | 18 | +test('DecompressStream deflat emits error on trailing data', async () => { | | 19 | + const valid = new Uint8Array([120, 156, 75, 4, 0, 0, 98, 0, 98]); // deflate('a') | | 20 | + const empty = new Uint8Array(1); | | 21 | + const invalid = new Uint8Array([...valid, ...empty]); | | 22 | + const double = new Uint8Array([...valid, ...valid]); | | 23 | + | | 24 | + for (const chunk of [[invalid], [valid, empty], [valid, valid], [valid, double]]) { | | 25 | + await expectTypeError( | | 26 | + Array.fromAsync( | | 27 | + new Blob([chunk]).stream().pipeThrough(new DecompressionStream('deflate')) | | 28 | + ) | | 29 | + ); | | 30 | + } | | 31 | +}); | | 32 | + | | 33 | +test('DecompressStream gzip emits error on trailing data', async () => { | | 34 | + const valid = new Uint8Array([31, 139, 8, 0, 0, 0, 0, 0, 0, 19, 75, 4, | | 35 | + 0, 67, 190, 183, 232, 1, 0, 0, 0]); // gzip('a') | | 36 | + const empty = new Uint8Array(1); | | 37 | + const invalid = new Uint8Array([...valid, ...empty]); | | 38 | + const double = new Uint8Array([...valid, ...valid]); | | 39 | + for (const chunk of [[invalid], [valid, empty], [valid, valid], [double]]) { | | 40 | + await expectTypeError( | | 41 | + Array.fromAsync( | | 42 | + new Blob([chunk]).stream().pipeThrough(new DecompressionStream('gzip')) | | 43 | + ) | | 44 | + ); | | 45 | + } | | 46 | +}); |
Collapse file Copy file name to clipboardExpand all lines: test/wpt/status/compression.json -8Lines changed: 0 additions & 8 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 11 | 11 | "compression-with-detach.tentative.window.js": { | 12 | 12 | "requires": ["crypto"] | 13 | 13 | }, | 14 | | - "decompression-corrupt-input.tentative.any.js": { | 15 | | - "fail": { | 16 | | - "expected": [ | 17 | | - "trailing junk for 'deflate' should give an error", | 18 | | - "trailing junk for 'gzip' should give an error" | 19 | | - ] | 20 | | - } | 21 | | - }, | 22 | 14 | "idlharness-shadowrealm.window.js": { | 23 | 15 | "skip": "ShadowRealm support is not enabled" | 24 | 16 | }, | |
|