Skip to content

Commit fed4f81

Browse files
committed
Bug 1990921 - Enqueue the decode result even if stream end is met r=smaug
This corresponds to the not-merged-yet spec PR: whatwg/compression#77 Differential Revision: https://phabricator.services.mozilla.com/D266433
1 parent 958189c commit fed4f81

10 files changed

+147
-79
lines changed

dom/base/DecompressionStream.cpp

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,6 @@ class ZLibDecompressionStreamAlgorithms : public DecompressionStreamAlgorithms {
201201
// after stream end.
202202
// Note that additional calls for inflate() immediately emits
203203
// Z_STREAM_END after this point.
204-
if (mZStream.avail_in > 0) {
205-
aRv.ThrowTypeError("Unexpected input after the end of stream");
206-
return;
207-
}
208204
mObservedStreamEnd = true;
209205
break;
210206
case Z_OK:
@@ -255,15 +251,6 @@ class ZLibDecompressionStreamAlgorithms : public DecompressionStreamAlgorithms {
255251
// * inflate() should normally be called until it returns Z_STREAM_END or an
256252
// error.
257253

258-
if (aFlush == Flush::Yes && !mObservedStreamEnd) {
259-
// Step 2 of
260-
// https://wicg.github.io/compression/#decompress-flush-and-enqueue
261-
// If the end of the compressed input has not been reached, then throw a
262-
// TypeError.
263-
aRv.ThrowTypeError("The input is ended without reaching the stream end");
264-
return;
265-
}
266-
267254
// Step 5: For each Uint8Array array, enqueue array in ds's transform.
268255
for (const auto& view : array) {
269256
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*view));
@@ -272,6 +259,22 @@ class ZLibDecompressionStreamAlgorithms : public DecompressionStreamAlgorithms {
272259
return;
273260
}
274261
}
262+
263+
// Step 6: If the end of the compressed input has been reached, and ds's
264+
// context has not fully consumed chunk, then throw a TypeError.
265+
if (mObservedStreamEnd && mZStream.avail_in > 0) {
266+
aRv.ThrowTypeError("Unexpected input after the end of stream");
267+
return;
268+
}
269+
270+
// Step 3 of
271+
// https://wicg.github.io/compression/#decompress-flush-and-enqueue
272+
// If the end of the compressed input has not been reached, then throw a
273+
// TypeError.
274+
if (aFlush == Flush::Yes && !mObservedStreamEnd) {
275+
aRv.ThrowTypeError("The input is ended without reaching the stream end");
276+
return;
277+
}
275278
}
276279

277280
~ZLibDecompressionStreamAlgorithms() override { inflateEnd(&mZStream); }
@@ -345,7 +348,7 @@ class ZstdDecompressionStreamAlgorithms : public DecompressionStreamAlgorithms {
345348

346349
JS::RootedVector<JSObject*> array(aCx);
347350

348-
while (inBuffer.pos < inBuffer.size) {
351+
while (inBuffer.pos < inBuffer.size && !mObservedStreamEnd) {
349352
UniquePtr<uint8_t[], JS::FreePolicy> buffer(
350353
static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize)));
351354
if (!buffer) {
@@ -366,10 +369,6 @@ class ZstdDecompressionStreamAlgorithms : public DecompressionStreamAlgorithms {
366369

367370
if (rv == 0) {
368371
mObservedStreamEnd = true;
369-
if (inBuffer.pos < inBuffer.size) {
370-
aRv.ThrowTypeError("Unexpected input after the end of stream");
371-
return;
372-
}
373372
}
374373

375374
// Step 3: If buffer is empty, return.
@@ -391,15 +390,6 @@ class ZstdDecompressionStreamAlgorithms : public DecompressionStreamAlgorithms {
391390
}
392391
}
393392

394-
if (aFlush == Flush::Yes && !mObservedStreamEnd) {
395-
// Step 2 of
396-
// https://wicg.github.io/compression/#decompress-flush-and-enqueue
397-
// If the end of the compressed input has not been reached, then throw a
398-
// TypeError.
399-
aRv.ThrowTypeError("The input is ended without reaching the stream end");
400-
return;
401-
}
402-
403393
// Step 5: For each Uint8Array array, enqueue array in ds's transform.
404394
for (const auto& view : array) {
405395
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*view));
@@ -408,6 +398,22 @@ class ZstdDecompressionStreamAlgorithms : public DecompressionStreamAlgorithms {
408398
return;
409399
}
410400
}
401+
402+
// Step 6: If the end of the compressed input has been reached, and ds's
403+
// context has not fully consumed chunk, then throw a TypeError.
404+
if (mObservedStreamEnd && inBuffer.pos < inBuffer.size) {
405+
aRv.ThrowTypeError("Unexpected input after the end of stream");
406+
return;
407+
}
408+
409+
// Step 3 of
410+
// https://wicg.github.io/compression/#decompress-flush-and-enqueue
411+
// If the end of the compressed input has not been reached, then throw a
412+
// TypeError.
413+
if (aFlush == Flush::Yes && !mObservedStreamEnd) {
414+
aRv.ThrowTypeError("The input is ended without reaching the stream end");
415+
return;
416+
}
411417
}
412418

413419
~ZstdDecompressionStreamAlgorithms() override {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[decompression-correct-input.any.html]
2+
3+
[decompression-correct-input.any.shadowrealm.html]
4+
expected: ERROR
5+
6+
[decompression-correct-input.any.sharedworker.html]
7+
8+
[decompression-correct-input.any.serviceworker.html]
9+
10+
[decompression-correct-input.any.worker.html]
11+
expected:
12+
if not sessionHistoryInParent and not debug: [OK, TIMEOUT]
13+
14+
[decompression-correct-input.any.shadowrealm-in-sharedworker.html]
15+
expected: ERROR
16+
17+
[decompression-correct-input.any.shadowrealm-in-shadowrealm.html]
18+
expected: ERROR
19+
20+
[decompression-correct-input.any.shadowrealm-in-window.html]
21+
expected: ERROR
22+
23+
[decompression-correct-input.https.any.shadowrealm-in-audioworklet.html]
24+
expected: ERROR
25+
26+
[decompression-correct-input.https.any.shadowrealm-in-serviceworker.html]
27+
expected: [ERROR, TIMEOUT]
28+
29+
[decompression-correct-input.any.shadowrealm-in-dedicatedworker.html]
30+
expected: ERROR

testing/web-platform/meta/compression/decompression-correct-input.tentative.any.js.ini

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[decompression-extra-input.any.html]
2+
3+
[decompression-extra-input.https.any.shadowrealm-in-serviceworker.html]
4+
expected: ERROR
5+
6+
[decompression-extra-input.any.sharedworker.html]
7+
8+
[decompression-extra-input.https.any.shadowrealm-in-audioworklet.html]
9+
expected: ERROR
10+
11+
[decompression-extra-input.any.serviceworker.html]
12+
13+
[decompression-extra-input.any.shadowrealm-in-shadowrealm.html]
14+
expected: ERROR
15+
16+
[decompression-extra-input.any.shadowrealm-in-sharedworker.html]
17+
expected: ERROR
18+
19+
[decompression-extra-input.any.shadowrealm-in-dedicatedworker.html]
20+
expected: ERROR
21+
22+
[decompression-extra-input.any.worker.html]
23+
24+
[decompression-extra-input.any.shadowrealm-in-window.html]
25+
expected: ERROR
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
// META: global=window,worker
2+
// META: script=decompression-correct-input.js
23

34
"use strict";
45

5-
// The zstd-compressed bytes for the string "expected output".
6-
const compressedZstdBytes = new Uint8Array([
7-
0x28, 0xb5, 0x2f, 0xfd, 0x04, 0x58, 0x79, 0x00, 0x00, 0x65, 0x78, 0x70, 0x65,
8-
0x63, 0x74, 0x65, 0x64, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5b, 0x11,
9-
0xc6, 0x85,
10-
]);
11-
126
promise_test(async t => {
137
const ds = new DecompressionStream("zstd");
148
const writer = ds.writable.getWriter();
@@ -27,10 +21,5 @@ promise_test(async t => {
2721
await writePromise;
2822
await writerClosePromise;
2923

30-
const decompressedText = new TextDecoder().decode(value);
31-
assert_equals(
32-
decompressedText,
33-
"expected output",
34-
"The decompressed text should match the expected text"
35-
);
24+
assert_array_equals(Array.from(value), trueChunkValue, "value should match");
3625
}, "decompressing valid zstd input should yield 'expected output'");
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// The zstd-compressed bytes for the string "expected output".
2+
const compressedZstdBytes = new Uint8Array([
3+
0x28, 0xb5, 0x2f, 0xfd, 0x04, 0x58, 0x79, 0x00, 0x00, 0x65, 0x78, 0x70, 0x65,
4+
0x63, 0x74, 0x65, 0x64, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5b, 0x11,
5+
0xc6, 0x85,
6+
]);
7+
const trueChunkValue = new TextEncoder().encode('expected output');
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// META: global=window,worker
2+
// META: script=decompression-correct-input.js
3+
4+
'use strict';
5+
6+
const tests = [
7+
["zstd", new Uint8Array([...compressedZstdBytes, 0])],
8+
];
9+
10+
for (const [format, chunk] of tests) {
11+
promise_test(async t => {
12+
const ds = new DecompressionStream(format);
13+
const reader = ds.readable.getReader();
14+
const writer = ds.writable.getWriter();
15+
writer.write(chunk).catch(() => { });
16+
const { done, value } = await reader.read();
17+
assert_array_equals(Array.from(value), trueChunkValue, "value should match");
18+
await promise_rejects_js(t, TypeError, reader.read(), "Extra input should eventually throw");
19+
}, `decompressing ${format} input with extra pad should still give the output`);
20+
}
Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
11
// META: global=window,worker,shadowrealm
2+
// META: script=decompression-correct-input.js
23

34
'use strict';
45

5-
const deflateChunkValue = new Uint8Array([120, 156, 75, 173, 40, 72, 77, 46, 73, 77, 81, 200, 47, 45, 41, 40, 45, 1, 0, 48, 173, 6, 36]);
6-
const gzipChunkValue = new Uint8Array([31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 75, 173, 40, 72, 77, 46, 73, 77, 81, 200, 47, 45, 41, 40, 45, 1, 0, 176, 1, 57, 179, 15, 0, 0, 0]);
7-
const deflateRawChunkValue = new Uint8Array([
8-
0x4b, 0xad, 0x28, 0x48, 0x4d, 0x2e, 0x49, 0x4d, 0x51, 0xc8,
9-
0x2f, 0x2d, 0x29, 0x28, 0x2d, 0x01, 0x00,
10-
]);
11-
const trueChunkValue = new TextEncoder().encode('expected output');
12-
136
promise_test(async t => {
147
const ds = new DecompressionStream('deflate');
158
const reader = ds.readable.getReader();
@@ -19,7 +12,6 @@ promise_test(async t => {
1912
assert_array_equals(Array.from(value), trueChunkValue, "value should match");
2013
}, 'decompressing deflated input should work');
2114

22-
2315
promise_test(async t => {
2416
const ds = new DecompressionStream('gzip');
2517
const reader = ds.readable.getReader();
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const deflateChunkValue = new Uint8Array([120, 156, 75, 173, 40, 72, 77, 46, 73, 77, 81, 200, 47, 45, 41, 40, 45, 1, 0, 48, 173, 6, 36]);
2+
const gzipChunkValue = new Uint8Array([31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 75, 173, 40, 72, 77, 46, 73, 77, 81, 200, 47, 45, 41, 40, 45, 1, 0, 176, 1, 57, 179, 15, 0, 0, 0]);
3+
const deflateRawChunkValue = new Uint8Array([
4+
0x4b, 0xad, 0x28, 0x48, 0x4d, 0x2e, 0x49, 0x4d, 0x51, 0xc8,
5+
0x2f, 0x2d, 0x29, 0x28, 0x2d, 0x01, 0x00,
6+
]);
7+
const trueChunkValue = new TextEncoder().encode('expected output');
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// META: global=window,worker,shadowrealm
2+
// META: script=decompression-correct-input.js
3+
4+
'use strict';
5+
6+
const tests = [
7+
["deflate", new Uint8Array([...deflateChunkValue, 0])],
8+
["gzip", new Uint8Array([...gzipChunkValue, 0])],
9+
["deflate-raw", new Uint8Array([...deflateRawChunkValue, 0])],
10+
];
11+
12+
for (const [format, chunk] of tests) {
13+
promise_test(async t => {
14+
const ds = new DecompressionStream(format);
15+
const reader = ds.readable.getReader();
16+
const writer = ds.writable.getWriter();
17+
writer.write(chunk).catch(() => { });
18+
const { done, value } = await reader.read();
19+
assert_array_equals(Array.from(value), trueChunkValue, "value should match");
20+
await promise_rejects_js(t, TypeError, reader.read(), "Extra input should eventually throw");
21+
}, `decompressing ${format} input with extra pad should still give the output`);
22+
}

0 commit comments

Comments
 (0)