Files
sousa-gecko/testing/web-platform/tests/compression/decompression-corrupt-input.any.js
T
Anne van Kesteren 47a27e9af8 Bug 2038779 [wpt PR 59794] - Remove ShadowRealm test coverage, a=testonly
Automatic update from web-platform-tests
Remove ShadowRealm test coverage

This intentionally does not make infrastructure or documentation changes, but without implementer support all the ShadowRealm tests add significant noise. Once there is implementer support this can be reverted.

--

wpt-commits: e4a4672e9e607fc2b28e7173b83ce4e38ef53071
wpt-pr: 59794
2026-05-13 04:24:16 +00:00

325 lines
7.8 KiB
JavaScript

// META: global=window,worker
// META: script=resources/decompression-input.js
// This test checks that DecompressionStream behaves according to the standard
// when the input is corrupted. To avoid a combinatorial explosion in the
// number of tests, we only mutate one field at a time, and we only test
// "interesting" values.
'use strict';
// The many different cases are summarised in this data structure.
const expectations = [
{
format: 'deflate',
// Decompresses to 'expected output'.
baseInput: deflateChunkValue,
// See RFC1950 for the definition of the various fields used by deflate:
// https://tools.ietf.org/html/rfc1950.
fields: [
{
// The function of this field. This matches the name used in the RFC.
name: 'CMF',
// The offset of the field in bytes from the start of the input.
offset: 0,
// The length of the field in bytes.
length: 1,
cases: [
{
// The value to set the field to. If the field contains multiple
// bytes, all the bytes will be set to this value.
value: 0,
// The expected result. 'success' means the input is decoded
// successfully. 'error' means that the stream will be errored.
result: 'error'
}
]
},
{
name: 'FLG',
offset: 1,
length: 1,
// FLG contains a 4-bit checksum (FCHECK) which is calculated in such a
// way that there are 4 valid values for this field.
cases: [
{
value: 218,
result: 'success'
},
{
value: 1,
result: 'success'
},
{
value: 94,
result: 'success'
},
{
// The remaining 252 values cause an error.
value: 157,
result: 'error'
}
]
},
{
name: 'DATA',
// In general, changing any bit of the data will trigger a checksum
// error. Only the last byte does anything else.
offset: 18,
length: 1,
cases: [
{
value: 4,
result: 'success'
},
{
value: 5,
result: 'error'
}
]
},
{
name: 'ADLER',
offset: -4,
length: 4,
cases: [
{
value: 255,
result: 'error'
}
]
}
]
},
{
format: 'gzip',
// Decompresses to 'expected output'.
baseInput: gzipChunkValue,
// See RFC1952 for the definition of the various fields used by gzip:
// https://tools.ietf.org/html/rfc1952.
fields: [
{
name: 'ID',
offset: 0,
length: 2,
cases: [
{
value: 255,
result: 'error'
}
]
},
{
name: 'CM',
offset: 2,
length: 1,
cases: [
{
value: 0,
result: 'error'
}
]
},
{
name: 'FLG',
offset: 3,
length: 1,
cases: [
{
value: 1, // FTEXT
result: 'success'
},
{
value: 2, // FHCRC
result: 'error'
}
]
},
{
name: 'MTIME',
offset: 4,
length: 4,
cases: [
{
// Any value is valid for this field.
value: 255,
result: 'success'
}
]
},
{
name: 'XFL',
offset: 8,
length: 1,
cases: [
{
// Any value is accepted.
value: 255,
result: 'success'
}
]
},
{
name: 'OS',
offset: 9,
length: 1,
cases: [
{
// Any value is accepted.
value: 128,
result: 'success'
}
]
},
{
name: 'DATA',
// The last byte of the data is the most interesting.
offset: 26,
length: 1,
cases: [
{
value: 3,
result: 'error'
},
{
value: 4,
result: 'success'
}
]
},
{
name: 'CRC',
offset: -8,
length: 4,
cases: [
{
// Any change will error the stream.
value: 0,
result: 'error'
}
]
},
{
name: 'ISIZE',
offset: -4,
length: 4,
cases: [
{
// A mismatch will error the stream.
value: 1,
result: 'error'
}
]
}
]
},
{
format: 'brotli',
// Decompresses to 'expected output'.
baseInput: brotliChunkValue,
fields: []
}
];
async function tryDecompress(input, format) {
const ds = new DecompressionStream(format);
const reader = ds.readable.getReader();
const writer = ds.writable.getWriter();
writer.write(input).catch(() => {});
writer.close().catch(() => {});
let out = [];
while (true) {
try {
const { value, done } = await reader.read();
if (done) {
break;
}
out = out.concat(Array.from(value));
} catch (e) {
if (e instanceof TypeError) {
return { result: 'error' };
} else {
return { result: e.name };
}
}
}
const expectedOutput = 'expected output';
if (out.length !== expectedOutput.length) {
return { result: 'corrupt' };
}
for (let i = 0; i < out.length; ++i) {
if (out[i] !== expectedOutput.charCodeAt(i)) {
return { result: 'corrupt' };
}
}
return { result: 'success' };
}
function corruptInput(input, offset, length, value) {
const output = new Uint8Array(input);
if (offset < 0) {
offset += input.length;
}
for (let i = offset; i < offset + length; ++i) {
output[i] = value;
}
return output;
}
for (const { format, baseInput, fields } of expectations) {
promise_test(async () => {
const { result } = await tryDecompress(baseInput, format);
assert_equals(result, 'success', 'decompression should succeed');
}, `the unchanged input for '${format}' should decompress successfully`);
promise_test(async () => {
const truncatedInput = baseInput.subarray(0, -1);
const { result } = await tryDecompress(truncatedInput, format);
assert_equals(result, 'error', 'decompression should fail');
}, `truncating the input for '${format}' should give an error`);
promise_test(async () => {
const extendedInput = new Uint8Array([...baseInput, 0]);
const { result } = await tryDecompress(extendedInput, format);
assert_equals(result, 'error', 'decompression should fail');
}, `trailing junk for '${format}' should give an error`);
for (const { name, offset, length, cases } of fields) {
for (const { value, result } of cases) {
promise_test(async () => {
const corruptedInput = corruptInput(baseInput, offset, length, value);
const { result: actual } =
await tryDecompress(corruptedInput, format);
assert_equals(actual, result, 'result should match');
}, `format '${format}' field ${name} should be ${result} for ${value}`);
}
}
}
promise_test(async () => {
// Data generated in Python:
// ```py
// h = b"thequickbrownfoxjumped\x00"
// words = h.split()
// zdict = b''.join(words)
// co = zlib.compressobj(zdict=zdict)
// cd = co.compress(h) + co.flush()
// ```
const { result } = await tryDecompress(new Uint8Array([
0x78, 0xbb, 0x74, 0xee, 0x09, 0x59, 0x2b, 0xc1, 0x2e, 0x0c, 0x00, 0x74, 0xee, 0x09, 0x59
]), 'deflate');
assert_equals(result, 'error', 'Data compressed with a dictionary should throw TypeError');
}, 'the deflate input compressed with dictionary should give an error')