diff --git a/test/test_html_escape/test_html_escape.cpp b/test/test_html_escape/test_html_escape.cpp index 2f782cc..bec1753 100644 --- a/test/test_html_escape/test_html_escape.cpp +++ b/test/test_html_escape/test_html_escape.cpp @@ -12,25 +12,121 @@ static void test_html_escape_basic() { TEST_ASSERT_EQUAL_STRING("&<>"'", html_escape("&<>\"'").c_str()); } -static void test_sanitize_device_id() { +static void test_html_escape_adversarial() { + TEST_ASSERT_EQUAL_STRING("&amp;", html_escape("&").c_str()); + TEST_ASSERT_EQUAL_STRING("\n\r\t", html_escape("\n\r\t").c_str()); + + const String chunk = "<&>\"'abc\n\r\t"; + const String escaped_chunk = "<&>"'abc\n\r\t"; + const size_t repeats = 300; // 3.3 KB input + String input; + String expected; + input.reserve(chunk.length() * repeats); + expected.reserve(escaped_chunk.length() * repeats); + for (size_t i = 0; i < repeats; ++i) { + input += chunk; + expected += escaped_chunk; + } + + String out = html_escape(input); + TEST_ASSERT_EQUAL_UINT(expected.length(), out.length()); + TEST_ASSERT_EQUAL_STRING(expected.c_str(), out.c_str()); + TEST_ASSERT_TRUE(out.indexOf("<&>"'abc") >= 0); +} + +static void test_url_encode_component_table() { + struct Case { + const char *input; + const char *expected; + }; + + const Case cases[] = { + {"", ""}, + {"abcABC012-_.~", "abcABC012-_.~"}, + {"a b", "a%20b"}, + {"/\\?&#%\"'", "%2F%5C%3F%26%23%25%22%27"}, + {"line\nbreak", "line%0Abreak"}, + }; + + for (size_t i = 0; i < (sizeof(cases) / sizeof(cases[0])); ++i) { + String out = url_encode_component(cases[i].input); + TEST_ASSERT_EQUAL_STRING(cases[i].expected, out.c_str()); + } + + String control; + control += static_cast(0x01); + control += static_cast(0x1F); + control += static_cast(0x7F); + TEST_ASSERT_EQUAL_STRING("%01%1F%7F", url_encode_component(control).c_str()); + + const String long_chunk = "AZaz09-_.~ /%?"; + const String long_expected_chunk = "AZaz09-_.~%20%2F%25%3F"; + String long_input; + String long_expected; + for (size_t i = 0; i < 40; ++i) { // 520 chars + long_input += long_chunk; + long_expected += long_expected_chunk; + } + String long_out_1 = url_encode_component(long_input); + String long_out_2 = url_encode_component(long_input); + TEST_ASSERT_EQUAL_STRING(long_expected.c_str(), long_out_1.c_str()); + TEST_ASSERT_EQUAL_STRING(long_out_1.c_str(), long_out_2.c_str()); +} + +static void test_sanitize_device_id_accepts_and_normalizes() { String out; - TEST_ASSERT_TRUE(sanitize_device_id("F19C", out)); - TEST_ASSERT_EQUAL_STRING("dd3-F19C", out.c_str()); - TEST_ASSERT_TRUE(sanitize_device_id("dd3-f19c", out)); - TEST_ASSERT_EQUAL_STRING("dd3-F19C", out.c_str()); - TEST_ASSERT_FALSE(sanitize_device_id("F19G", out)); - TEST_ASSERT_FALSE(sanitize_device_id("dd3-12", out)); - TEST_ASSERT_FALSE(sanitize_device_id("dd3-12345", out)); - TEST_ASSERT_FALSE(sanitize_device_id("../F19C", out)); - TEST_ASSERT_FALSE(sanitize_device_id("dd3-%2f", out)); - TEST_ASSERT_FALSE(sanitize_device_id("dd3-12/3", out)); - TEST_ASSERT_FALSE(sanitize_device_id("dd3-12\\3", out)); + const char *accept_cases[] = { + "F19C", + "f19c", + " f19c ", + "dd3-f19c", + "dd3-F19C", + "dd3-a0b1", + }; + + for (size_t i = 0; i < (sizeof(accept_cases) / sizeof(accept_cases[0])); ++i) { + TEST_ASSERT_TRUE(sanitize_device_id(accept_cases[i], out)); + if (String(accept_cases[i]).indexOf("a0b1") >= 0) { + TEST_ASSERT_EQUAL_STRING("dd3-A0B1", out.c_str()); + } else { + TEST_ASSERT_EQUAL_STRING("dd3-F19C", out.c_str()); + } + } +} + +static void test_sanitize_device_id_rejects_invalid() { + String out = "dd3-KEEP"; + const char *reject_cases[] = { + "", + "F", + "FFF", + "FFFFF", + "dd3-12", + "dd3-12345", + "F1 9C", + "dd3-F1\t9C", + "dd3-F19C%00", + "%F19C", + "../F19C", + "dd3-..1A", + "dd3-12/3", + "dd3-12\\3", + "F19G", + "dd3-zzzz", + }; + for (size_t i = 0; i < (sizeof(reject_cases) / sizeof(reject_cases[0])); ++i) { + TEST_ASSERT_FALSE(sanitize_device_id(reject_cases[i], out)); + } + TEST_ASSERT_EQUAL_STRING("dd3-KEEP", out.c_str()); } void setup() { UNITY_BEGIN(); RUN_TEST(test_html_escape_basic); - RUN_TEST(test_sanitize_device_id); + RUN_TEST(test_html_escape_adversarial); + RUN_TEST(test_url_encode_component_table); + RUN_TEST(test_sanitize_device_id_accepts_and_normalizes); + RUN_TEST(test_sanitize_device_id_rejects_invalid); UNITY_END(); }