| #include "unity/unity.h" |
| #include <libxml/HTMLparser.h> |
|
|
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
|
|
| |
| extern void test_htmlParseDocTypeDecl(htmlParserCtxtPtr ctxt); |
|
|
| |
| static int cap_called = 0; |
| static xmlChar *cap_name = NULL; |
| static xmlChar *cap_public = NULL; |
| static xmlChar *cap_system = NULL; |
|
|
| static void capture_reset(void) { |
| if (cap_name) { xmlFree(cap_name); cap_name = NULL; } |
| if (cap_public) { xmlFree(cap_public); cap_public = NULL; } |
| if (cap_system) { xmlFree(cap_system); cap_system = NULL; } |
| cap_called = 0; |
| } |
|
|
| static void capture_internalSubset(void *ctx, |
| const xmlChar *name, |
| const xmlChar *ExternalID, |
| const xmlChar *SystemID) { |
| (void)ctx; |
| cap_called++; |
| cap_name = (name != NULL) ? xmlStrdup(name) : NULL; |
| cap_public = (ExternalID != NULL) ? xmlStrdup(ExternalID) : NULL; |
| cap_system = (SystemID != NULL) ? xmlStrdup(SystemID) : NULL; |
| } |
|
|
| static void attach_sax(htmlParserCtxtPtr ctxt, xmlSAXHandler *sax) { |
| memset(sax, 0, sizeof(*sax)); |
| sax->initialized = XML_SAX2_MAGIC; |
| sax->internalSubset = capture_internalSubset; |
| |
| ctxt->sax = sax; |
| ctxt->disableSAX = 0; |
| } |
|
|
| static htmlParserCtxtPtr make_ctxt(const char *input, int options, xmlSAXHandler *saxOut) { |
| htmlParserCtxtPtr ctxt = htmlCreateMemoryParserCtxt(input, (int)strlen(input)); |
| TEST_ASSERT_NOT_NULL_MESSAGE(ctxt, "Failed to create HTML parser context"); |
| if (options != 0) { |
| int rc = htmlCtxtUseOptions(ctxt, options); |
| (void)rc; |
| } |
| attach_sax(ctxt, saxOut); |
| return ctxt; |
| } |
|
|
| void setUp(void) { |
| |
| xmlInitParser(); |
| capture_reset(); |
| } |
|
|
| void tearDown(void) { |
| capture_reset(); |
| |
| } |
|
|
| |
| static void assert_xml_equals(const xmlChar *actual, const char *expected) { |
| TEST_ASSERT_NOT_NULL_MESSAGE(actual, "Expected non-NULL xmlChar*"); |
| TEST_ASSERT_TRUE_MESSAGE(xmlStrEqual(actual, BAD_CAST expected), "xmlChar string mismatch"); |
| } |
|
|
| |
| void test_htmlParseDocTypeDecl_basic_invokes_callback(void) { |
| const char *buf = "<!DOCTYPE html>"; |
| xmlSAXHandler sax; |
| htmlParserCtxtPtr ctxt = make_ctxt(buf, 0, &sax); |
|
|
| test_htmlParseDocTypeDecl(ctxt); |
|
|
| TEST_ASSERT_EQUAL_INT(1, cap_called); |
| assert_xml_equals(cap_name, "html"); |
| TEST_ASSERT_NULL(cap_public); |
| TEST_ASSERT_NULL(cap_system); |
|
|
| htmlFreeParserCtxt(ctxt); |
| } |
|
|
| |
| void test_htmlParseDocTypeDecl_html5_lowers_name(void) { |
| const char *buf = "<!DOCTYPE HTML>"; |
| xmlSAXHandler sax; |
| htmlParserCtxtPtr ctxt = make_ctxt(buf, HTML_PARSE_HTML5, &sax); |
|
|
| test_htmlParseDocTypeDecl(ctxt); |
|
|
| TEST_ASSERT_EQUAL_INT(1, cap_called); |
| assert_xml_equals(cap_name, "html"); |
| TEST_ASSERT_NULL(cap_public); |
| TEST_ASSERT_NULL(cap_system); |
|
|
| htmlFreeParserCtxt(ctxt); |
| } |
|
|
| |
| void test_htmlParseDocTypeDecl_no_html5_preserves_case(void) { |
| const char *buf = "<!DOCTYPE Html>"; |
| xmlSAXHandler sax; |
| htmlParserCtxtPtr ctxt = make_ctxt(buf, 0, &sax); |
|
|
| test_htmlParseDocTypeDecl(ctxt); |
|
|
| TEST_ASSERT_EQUAL_INT(1, cap_called); |
| assert_xml_equals(cap_name, "Html"); |
| TEST_ASSERT_NULL(cap_public); |
| TEST_ASSERT_NULL(cap_system); |
|
|
| htmlFreeParserCtxt(ctxt); |
| } |
|
|
| |
| void test_htmlParseDocTypeDecl_public_and_system_ids(void) { |
| const char *pub = "-//W3C//DTD HTML 4.01//EN"; |
| const char *sys = "http://www.w3.org/TR/html4/strict.dtd"; |
| char buf[512]; |
| snprintf(buf, sizeof(buf), "<!DOCTYPE html PUBLIC \"%s\" \"%s\">", pub, sys); |
|
|
| xmlSAXHandler sax; |
| htmlParserCtxtPtr ctxt = make_ctxt(buf, 0, &sax); |
|
|
| test_htmlParseDocTypeDecl(ctxt); |
|
|
| TEST_ASSERT_EQUAL_INT(1, cap_called); |
| assert_xml_equals(cap_name, "html"); |
| assert_xml_equals(cap_public, pub); |
| assert_xml_equals(cap_system, sys); |
|
|
| htmlFreeParserCtxt(ctxt); |
| } |
|
|
| |
| void test_htmlParseDocTypeDecl_system_only(void) { |
| const char *sys = "about:legacy-compat"; |
| char buf[256]; |
| snprintf(buf, sizeof(buf), "<!DOCTYPE html SYSTEM \"%s\">", sys); |
|
|
| xmlSAXHandler sax; |
| htmlParserCtxtPtr ctxt = make_ctxt(buf, 0, &sax); |
|
|
| test_htmlParseDocTypeDecl(ctxt); |
|
|
| TEST_ASSERT_EQUAL_INT(1, cap_called); |
| assert_xml_equals(cap_name, "html"); |
| TEST_ASSERT_NULL(cap_public); |
| assert_xml_equals(cap_system, sys); |
|
|
| htmlFreeParserCtxt(ctxt); |
| } |
|
|
| |
| void test_htmlParseDocTypeDecl_missing_name(void) { |
| const char *buf = "<!DOCTYPE>"; |
| xmlSAXHandler sax; |
| htmlParserCtxtPtr ctxt = make_ctxt(buf, 0, &sax); |
|
|
| test_htmlParseDocTypeDecl(ctxt); |
|
|
| TEST_ASSERT_EQUAL_INT(1, cap_called); |
| TEST_ASSERT_NULL(cap_name); |
| TEST_ASSERT_NULL(cap_public); |
| TEST_ASSERT_NULL(cap_system); |
|
|
| htmlFreeParserCtxt(ctxt); |
| } |
|
|
| |
| void test_htmlParseDocTypeDecl_bogus_missing_gt(void) { |
| const char *buf = "<!DOCTYPE html PUBLIC \"abc\" \"def\""; |
| xmlSAXHandler sax; |
| htmlParserCtxtPtr ctxt = make_ctxt(buf, 0, &sax); |
|
|
| test_htmlParseDocTypeDecl(ctxt); |
|
|
| TEST_ASSERT_EQUAL_INT(1, cap_called); |
| assert_xml_equals(cap_name, "html"); |
| assert_xml_equals(cap_public, "abc"); |
| assert_xml_equals(cap_system, "def"); |
|
|
| htmlFreeParserCtxt(ctxt); |
| } |
|
|
| |
| void test_htmlParseDocTypeDecl_advances_past_gt(void) { |
| const char *buf = "<!DOCTYPE html><html>"; |
| xmlSAXHandler sax; |
| htmlParserCtxtPtr ctxt = make_ctxt(buf, 0, &sax); |
|
|
| test_htmlParseDocTypeDecl(ctxt); |
|
|
| TEST_ASSERT_EQUAL_INT(1, cap_called); |
| |
| TEST_ASSERT_NOT_NULL(ctxt->input); |
| TEST_ASSERT_NOT_NULL(ctxt->input->cur); |
| TEST_ASSERT_TRUE_MESSAGE(*(ctxt->input->cur) == '<', "Parser cursor not advanced past '>' as expected"); |
|
|
| htmlFreeParserCtxt(ctxt); |
| } |
|
|
| int main(void) { |
| UNITY_BEGIN(); |
| RUN_TEST(test_htmlParseDocTypeDecl_basic_invokes_callback); |
| RUN_TEST(test_htmlParseDocTypeDecl_html5_lowers_name); |
| RUN_TEST(test_htmlParseDocTypeDecl_no_html5_preserves_case); |
| RUN_TEST(test_htmlParseDocTypeDecl_public_and_system_ids); |
| RUN_TEST(test_htmlParseDocTypeDecl_system_only); |
| RUN_TEST(test_htmlParseDocTypeDecl_missing_name); |
| RUN_TEST(test_htmlParseDocTypeDecl_bogus_missing_gt); |
| RUN_TEST(test_htmlParseDocTypeDecl_advances_past_gt); |
| return UNITY_END(); |
| } |