ladybird/Tests/LibWeb/Text/input/Encoding/TextDecoderStream-decode.html
Andreas Kling 01cec162c8 LibTextCodec: Stop buffering invalid UTF-8 tails
Treat trailing UTF-8 prefixes with an invalid second byte as complete
input for streaming decode, so replacement characters are emitted in the
current chunk instead of being held until later input or finish. Keep
valid incomplete prefixes buffered across chunk boundaries.

Keep TextDecoderStream from holding continuation bytes after an invalid
lead byte at a chunk boundary. Add LibTextCodec and TextDecoderStream
coverage for invalid tails, valid split sequences, EOF partials,
surrogate sequences, and malformed continuation tails.
2026-05-18 14:08:22 +02:00

54 lines
2.2 KiB
HTML

<!DOCTYPE html>
<script src="../include.js"></script>
<script>
function describe(string) {
const codePoints = Array.from(string, codePoint => codePoint.codePointAt(0).toString(16));
return `[${string.length}, ${codePoints.join(", ")}]`;
}
async function decodeChunks(chunks) {
const readable = new ReadableStream({
start(controller) {
for (const chunk of chunks)
controller.enqueue(new Uint8Array(chunk));
controller.close();
},
});
const reader = readable.pipeThrough(new TextDecoderStream("utf-8")).getReader();
const decoded = [];
for (;;) {
const { value, done } = await reader.read();
if (done)
break;
decoded.push(describe(value));
}
return decoded;
}
promiseTest(async () => {
const cases = [
["invalid_second_byte_e0_80", [[0xe0, 0x80]]],
["invalid_second_byte_f4_90", [[0xf4, 0x90]]],
["split_valid_2_byte", [[0xc3], [0xa9]]],
["split_valid_3_byte", [[0xe0, 0xa0], [0x80]]],
["split_valid_4_byte", [[0xf0, 0x90, 0x80], [0x80]]],
["eof_incomplete_2_byte", [[0xc3]]],
["eof_incomplete_3_byte", [[0xe0, 0xa0]]],
["eof_incomplete_4_byte", [[0xf0, 0x90, 0x80]]],
["ascii_prefix_then_eof_incomplete_3_byte", [[0x78, 0xe0, 0xa0]]],
["incomplete_prefix_then_ascii", [[0xe0, 0xa0], [0x78]]],
["complete_sequence_with_extra_continuation", [[0xc3, 0xa9, 0x80]]],
["surrogate_sequence", [[0xed, 0xa0, 0x80]]],
["invalid_lead_f5_with_continuations", [[0xf5, 0x80, 0x80, 0x80]]],
["overlong_c0_with_continuations", [[0xc0, 0x80, 0x80]]],
["continuation_only_tail", [[0x80, 0x80, 0x80, 0x80]]],
["invalid_lead_then_ascii", [[0xf5, 0x78]]],
["invalid_lead_with_continuations_then_ascii", [[0xf5, 0x80, 0x80, 0x80, 0x78]]],
];
for (const [name, chunks] of cases)
println(`${name}: ${(await decodeChunks(chunks)).join(" | ")}`);
});
</script>