Skip to content

Commit e8a07f2

Browse files
0hmXtargos
authored andcommitted
stream: making DecompressionStream spec compilent for trailing junk
Introduce `ERR_TRAILING_JUNK_AFTER_STREAM_END` error to handle unexpected data after the end of a compressed stream. This ensures proper error reporting when decompressing deflate or gzip streams with trailing junk. Added tests to verify the behavior. Fixes: #58247 PR-URL: #58316 Reviewed-By: Darshan Sen <[email protected]> Reviewed-By: Matthew Aitken <[email protected]>
1 parent 3a6bd9c commit e8a07f2

File tree

6 files changed

+83
-10
lines changed

6 files changed

+83
-10
lines changed

‎doc/api/errors.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3012,6 +3012,15 @@ category.
30123012
The `node:trace_events` module could not be loaded because Node.js was compiled
30133013
with the `--without-v8-platform` flag.
30143014

3015+
<aid="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+
30153024
<aid="ERR_TRANSFORM_ALREADY_TRANSFORMING"></a>
30163025

30173026
### `ERR_TRANSFORM_ALREADY_TRANSFORMING`

‎lib/internal/errors.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,6 +1803,8 @@ E('ERR_TRACE_EVENTS_CATEGORY_REQUIRED',
18031803
'At least one category is required',TypeError);
18041804
E('ERR_TRACE_EVENTS_UNAVAILABLE','Trace events are unavailable',Error);
18051805

1806+
E('ERR_TRAILING_JUNK_AFTER_STREAM_END','Trailing junk found after the end of the compressed stream',TypeError);
1807+
18061808
// This should probably be a `RangeError`.
18071809
E('ERR_TTY_INIT_FAILED','TTY initialization failed',SystemError);
18081810
E('ERR_UNAVAILABLE_DURING_EXIT','Cannot call function in process exit '+

‎lib/internal/webstreams/compression.js‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,16 +99,28 @@ class DecompressionStream{
9999
});
100100
switch(format){
101101
case'deflate':
102-
this.#handle =lazyZlib().createInflate();
102+
this.#handle =lazyZlib().createInflate({
103+
rejectGarbageAfterEnd: true,
104+
});
103105
break;
104106
case'deflate-raw':
105107
this.#handle =lazyZlib().createInflateRaw();
106108
break;
107109
case'gzip':
108-
this.#handle =lazyZlib().createGunzip();
110+
this.#handle =lazyZlib().createGunzip({
111+
rejectGarbageAfterEnd: true,
112+
});
109113
break;
110114
}
111115
this.#transform =newReadableWritablePairFromDuplex(this.#handle);
116+
117+
this.#handle.on('error',(err)=>{
118+
if(this.#transform?.writable&&
119+
!this.#transform.writable.locked&&
120+
typeofthis.#transform.writable.abort==='function'){
121+
this.#transform.writable.abort(err);
122+
}
123+
});
112124
}
113125

114126
/**

‎lib/zlib.js‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const{
4242
ERR_BUFFER_TOO_LARGE,
4343
ERR_INVALID_ARG_TYPE,
4444
ERR_OUT_OF_RANGE,
45+
ERR_TRAILING_JUNK_AFTER_STREAM_END,
4546
ERR_ZSTD_INVALID_PARAM,
4647
},
4748
genericNodeError,
@@ -266,6 +267,8 @@ function ZlibBase(opts, mode, handle,{flush, finishFlush, fullFlush }){
266267
this._defaultFullFlushFlag=fullFlush;
267268
this._info=opts?.info;
268269
this._maxOutputLength=maxOutputLength;
270+
271+
this._rejectGarbageAfterEnd=opts?.rejectGarbageAfterEnd===true;
269272
}
270273
ObjectSetPrototypeOf(ZlibBase.prototype,Transform.prototype);
271274
ObjectSetPrototypeOf(ZlibBase,Transform);
@@ -570,6 +573,14 @@ function processCallback(){
570573
// stream has ended early.
571574
// This applies to streams where we don't check data past the end of
572575
// what was consumed; that is, everything except Gunzip/Unzip.
576+
577+
if(self._rejectGarbageAfterEnd){
578+
consterr=newERR_TRAILING_JUNK_AFTER_STREAM_END();
579+
self.destroy(err);
580+
this.cb(err);
581+
return;
582+
}
583+
573584
self.push(null);
574585
}
575586

@@ -662,6 +673,7 @@ function Zlib(opts, mode){
662673

663674
this._level=level;
664675
this._strategy=strategy;
676+
this._mode=mode;
665677
}
666678
ObjectSetPrototypeOf(Zlib.prototype,ZlibBase.prototype);
667679
ObjectSetPrototypeOf(Zlib,ZlibBase);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
require('../common');
3+
constassert=require('assert').strict;
4+
consttest=require('node:test');
5+
const{ DecompressionStream }=require('stream/web');
6+
7+
asyncfunctionexpectTypeError(promise){
8+
letthrew=false;
9+
try{
10+
awaitpromise;
11+
}catch(err){
12+
threw=true;
13+
assert(errinstanceofTypeError,`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+
constvalid=newUint8Array([120,156,75,4,0,0,98,0,98]);// deflate('a')
20+
constempty=newUint8Array(1);
21+
constinvalid=newUint8Array([...valid, ...empty]);
22+
constdouble=newUint8Array([...valid, ...valid]);
23+
24+
for(constchunkof[[invalid],[valid,empty],[valid,valid],[valid,double]]){
25+
awaitexpectTypeError(
26+
Array.fromAsync(
27+
newBlob([chunk]).stream().pipeThrough(newDecompressionStream('deflate'))
28+
)
29+
);
30+
}
31+
});
32+
33+
test('DecompressStream gzip emits error on trailing data',async()=>{
34+
constvalid=newUint8Array([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+
constempty=newUint8Array(1);
37+
constinvalid=newUint8Array([...valid, ...empty]);
38+
constdouble=newUint8Array([...valid, ...valid]);
39+
for(constchunkof[[invalid],[valid,empty],[valid,valid],[double]]){
40+
awaitexpectTypeError(
41+
Array.fromAsync(
42+
newBlob([chunk]).stream().pipeThrough(newDecompressionStream('gzip'))
43+
)
44+
);
45+
}
46+
});

‎test/wpt/status/compression.json‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,6 @@
1111
"compression-with-detach.tentative.window.js":{
1212
"requires": ["crypto"]
1313
},
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-
},
2214
"idlharness-shadowrealm.window.js":{
2315
"skip": "ShadowRealm support is not enabled"
2416
},

0 commit comments

Comments
(0)