ladybird/Tests/LibTextCodec/TestTextDecoders.cpp
Andreas Kling c92079893a LibTextCodec: Match UTF-8 replacement handling
Consume truncated UTF-8 tails as one malformed sequence while preserving
existing behavior for encoded surrogate code points. This keeps lazy
SourceCode decoding and bytecode cache source hashing aligned with eager
source text decoding for invalid cached script source bytes.

Continue stripping an initial UTF-8 byte order mark in
UTF8Decoder::to_utf8() so HTML parsing keeps matching the previous
String-based implementation. Cover the shared decoder behavior and the
TextDecoder API surface.
2026-05-18 09:18:35 +02:00

217 lines
8.2 KiB
C++

/*
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibTest/TestCase.h>
#include <LibTextCodec/Decoder.h>
static ReadonlyBytes bytes(Vector<u8> const& data)
{
return data.span();
}
static TextCodec::Decoder& decoder_for(StringView encoding)
{
auto decoder = TextCodec::decoder_for(encoding);
VERIFY(decoder.has_value());
return decoder.value();
}
static Vector<u32> process_code_points(TextCodec::Decoder& decoder, StringView input)
{
Vector<u32> code_points;
MUST(decoder.process_code_points(input, [&](u32 code_point) -> ErrorOr<void> {
TRY(code_points.try_append(code_point));
return {};
}));
return code_points;
}
TEST_CASE(test_utf8_decode)
{
auto decoder = TextCodec::UTF8Decoder();
// Bytes for U+1F600 GRINNING FACE
auto test_string = "\xf0\x9f\x98\x80"sv;
EXPECT(decoder.validate(test_string));
Vector<u32> processed_code_points;
MUST(decoder.process(test_string, [&](u32 code_point) {
return processed_code_points.try_append(code_point);
}));
EXPECT(processed_code_points.size() == 1);
EXPECT(processed_code_points[0] == 0x1F600);
EXPECT(MUST(decoder.to_utf8(test_string)) == test_string);
}
TEST_CASE(test_utf8_process_code_points)
{
auto decoder = TextCodec::UTF8Decoder();
auto test_string = "A\xf0\x9f\x98\x80"sv;
EXPECT_EQ(process_code_points(decoder, test_string), (Vector<u32> { 0x41, 0x1F600 }));
}
TEST_CASE(test_utf8_process_code_points_replaces_surrogates)
{
auto decoder = TextCodec::UTF8Decoder();
auto utf8_encoded_surrogate_bytes = Vector<u8> { 0xed, 0xa0, 0x80 };
auto utf8_encoded_surrogate = StringView(bytes(utf8_encoded_surrogate_bytes));
EXPECT(!decoder.validate(utf8_encoded_surrogate));
EXPECT_EQ(MUST(decoder.to_utf8(utf8_encoded_surrogate)), "\xef\xbf\xbd"sv);
EXPECT_EQ(process_code_points(decoder, utf8_encoded_surrogate), (Vector<u32> { 0xfffd }));
}
TEST_CASE(test_utf8_process_code_points_replaces_truncated_tail_as_single_error)
{
auto decoder = TextCodec::UTF8Decoder();
auto truncated_tail_bytes = Vector<u8> { 0xf0, 0x9f, 0x98 };
auto truncated_tail = StringView(bytes(truncated_tail_bytes));
EXPECT(!decoder.validate(truncated_tail));
EXPECT_EQ(MUST(decoder.to_utf8(truncated_tail)), "\xef\xbf\xbd"sv);
EXPECT_EQ(process_code_points(decoder, truncated_tail), (Vector<u32> { 0xfffd }));
}
TEST_CASE(test_utf8_process_code_points_replaces_overlong_sequences)
{
auto decoder = TextCodec::UTF8Decoder();
auto overlong_null_bytes = Vector<u8> { 0xc0, 0x80 };
auto overlong_null = StringView(bytes(overlong_null_bytes));
EXPECT(!decoder.validate(overlong_null));
EXPECT_EQ(MUST(decoder.to_utf8(overlong_null)), "\xef\xbf\xbd\xef\xbf\xbd"sv);
EXPECT_EQ(process_code_points(decoder, overlong_null), (Vector<u32> { 0xfffd, 0xfffd }));
}
TEST_CASE(test_utf16be_decode)
{
auto decoder = TextCodec::UTF16BEDecoder();
// This is the output of `python3 -c "print('säk😀'.encode('utf-16be'))"`.
auto test_string = "\x00s\x00\xe4\x00k\xd8=\xde\x00"sv;
EXPECT(decoder.validate(test_string));
auto utf8 = MUST(decoder.to_utf8(test_string));
EXPECT_EQ(utf8, "säk😀"sv);
}
TEST_CASE(test_utf16be_process_code_points)
{
auto decoder = TextCodec::UTF16BEDecoder();
EXPECT_EQ(process_code_points(decoder, StringView(bytes({ 0xfe, 0xff, 0x00, 'A', 0xd8, 0x3d, 0xde, 0x00 }))), (Vector<u32> { 0x41, 0x1F600 }));
EXPECT_EQ(process_code_points(decoder, StringView(bytes({ 0xd8, 0x3d, 0x00, 'A', 0xde, 0x00 }))), (Vector<u32> { 0xfffd, 0x41, 0xfffd }));
}
TEST_CASE(test_streaming_decoder_utf8_mid_sequence)
{
auto& decoder = decoder_for("UTF-8"sv);
auto streaming_decoder = TextCodec::StreamingDecoder { decoder };
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes({ 'a', 0xc3 }))), "a"sv);
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes({ 0xa9, 'b' }))), "éb"sv);
EXPECT_EQ(MUST(streaming_decoder.finish()), ""sv);
}
TEST_CASE(test_streaming_decoder_finishes_incomplete_sequence)
{
auto& decoder = decoder_for("UTF-8"sv);
auto streaming_decoder = TextCodec::StreamingDecoder { decoder };
auto incomplete_sequence = Vector<u8> { 0xc3 };
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes(incomplete_sequence))), ""sv);
EXPECT_EQ(MUST(streaming_decoder.finish()), MUST(decoder.to_utf8(StringView(bytes(incomplete_sequence)))));
}
TEST_CASE(test_streaming_decoder_utf16_odd_byte)
{
auto& decoder = decoder_for("UTF-16LE"sv);
auto streaming_decoder = TextCodec::StreamingDecoder { decoder };
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes({ 0x41, 0x00, 0x42 }))), "A"sv);
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes({ 0x00 }))), "B"sv);
EXPECT_EQ(MUST(streaming_decoder.finish()), ""sv);
}
TEST_CASE(test_streaming_decoder_utf16_surrogate_pair_split)
{
auto& decoder = decoder_for("UTF-16BE"sv);
auto streaming_decoder = TextCodec::StreamingDecoder { decoder };
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes({ 0x00, 0x41, 0xd8, 0x3d }))), "A"sv);
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes({ 0xde, 0x00 }))), "😀"sv);
EXPECT_EQ(MUST(streaming_decoder.finish()), ""sv);
}
TEST_CASE(test_streaming_decoder_gb18030_four_byte_tail)
{
auto& decoder = decoder_for("gb18030"sv);
auto streaming_decoder = TextCodec::StreamingDecoder { decoder };
auto gb18030_sequence = Vector<u8> { 0x81, 0x30, 0x81, 0x30 };
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes({ 'a', 0x81, 0x30, 0x81 }))), "a"sv);
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes({ 0x30 }))), MUST(decoder.to_utf8(StringView(bytes(gb18030_sequence)))));
EXPECT_EQ(MUST(streaming_decoder.finish()), ""sv);
}
TEST_CASE(test_streaming_decoder_big5_overlapping_trail)
{
auto& decoder = decoder_for("Big5"sv);
auto streaming_decoder = TextCodec::StreamingDecoder { decoder };
auto big5_sequence = Vector<u8> { 0xa4, 0xa4 };
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes(big5_sequence))), MUST(decoder.to_utf8(StringView(bytes(big5_sequence)))));
EXPECT_EQ(MUST(streaming_decoder.finish()), ""sv);
auto streaming_decoder_for_split_input = TextCodec::StreamingDecoder { decoder };
EXPECT_EQ(MUST(streaming_decoder_for_split_input.to_utf8(bytes({ 0xa4 }))), ""sv);
EXPECT_EQ(MUST(streaming_decoder_for_split_input.to_utf8(bytes({ 0xa4 }))), MUST(decoder.to_utf8(StringView(bytes(big5_sequence)))));
EXPECT_EQ(MUST(streaming_decoder_for_split_input.finish()), ""sv);
}
TEST_CASE(test_streaming_decoder_euc_jp_three_byte_tail)
{
auto& decoder = decoder_for("EUC-JP"sv);
auto streaming_decoder = TextCodec::StreamingDecoder { decoder };
auto euc_jp_sequence = Vector<u8> { 0x8f, 0xa2, 0xaf };
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes({ 0x8f, 0xa2 }))), ""sv);
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes({ 0xaf }))), MUST(decoder.to_utf8(StringView(bytes(euc_jp_sequence)))));
EXPECT_EQ(MUST(streaming_decoder.finish()), ""sv);
}
TEST_CASE(test_streaming_decoder_shift_jis_tail)
{
auto& decoder = decoder_for("Shift_JIS"sv);
auto streaming_decoder = TextCodec::StreamingDecoder { decoder };
auto shift_jis_sequence = Vector<u8> { 0x82, 0xa0 };
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes({ 0x82 }))), ""sv);
EXPECT_EQ(MUST(streaming_decoder.to_utf8(bytes({ 0xa0 }))), MUST(decoder.to_utf8(StringView(bytes(shift_jis_sequence)))));
EXPECT_EQ(MUST(streaming_decoder.finish()), ""sv);
}
TEST_CASE(test_utf16le_decode)
{
auto decoder = TextCodec::UTF16LEDecoder();
// This is the output of `python3 -c "print('säk😀'.encode('utf-16le'))"`.
auto test_string = "s\x00\xe4\x00k\x00=\xd8\x00\xde"sv;
EXPECT(decoder.validate(test_string));
auto utf8 = MUST(decoder.to_utf8(test_string));
EXPECT_EQ(utf8, "säk😀"sv);
}
TEST_CASE(test_utf16le_process_code_points)
{
auto decoder = TextCodec::UTF16LEDecoder();
EXPECT_EQ(process_code_points(decoder, StringView(bytes({ 0xff, 0xfe, 'A', 0x00, 0x3d, 0xd8, 0x00, 0xde }))), (Vector<u32> { 0x41, 0x1F600 }));
EXPECT_EQ(process_code_points(decoder, StringView(bytes({ 0x3d, 0xd8, 'A', 0x00, 0x00, 0xde }))), (Vector<u32> { 0xfffd, 0x41, 0xfffd }));
}