375 lines
14 KiB
Diff
375 lines
14 KiB
Diff
From 271efb6069c5c979545cfbe00b738184c5632b93 Mon Sep 17 00:00:00 2001
|
|
From: Sebastian Pipping <sebastian@pipping.org>
|
|
Date: Wed, 21 Apr 2021 00:11:04 +0200
|
|
Subject: [PATCH] tests: Cover accounting
|
|
|
|
---
|
|
lib/internal.h | 6 +-
|
|
tests/runtests.c | 300 ++++++++++++++++++++++++++++++++++++++++++++++++-
|
|
2 files changed, 304 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/lib/internal.h b/lib/internal.h
|
|
index ce6d27a..377c12b 100644
|
|
--- a/lib/internal.h
|
|
+++ b/lib/internal.h
|
|
@@ -109,10 +109,12 @@
|
|
|
|
#if defined(_WIN32) && ! defined(__USE_MINGW_ANSI_STDIO)
|
|
# define EXPAT_FMT_ULL(midpart) "%" midpart "I64u"
|
|
-# if defined(_WIN64) // Note: modifier "td" does not work for MinGW
|
|
+# if defined(_WIN64) // Note: modifiers "td" and "zu" do not work for MinGW
|
|
# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "I64d"
|
|
+# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "I64u"
|
|
# else
|
|
# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "d"
|
|
+# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u"
|
|
# endif
|
|
#else
|
|
# define EXPAT_FMT_ULL(midpart) "%" midpart "llu"
|
|
@@ -120,8 +122,10 @@
|
|
# error Compiler did not define ULONG_MAX for us
|
|
# elif ULONG_MAX == 18446744073709551615u // 2^64-1
|
|
# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "ld"
|
|
+# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "lu"
|
|
# else
|
|
# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "d"
|
|
+# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u"
|
|
# endif
|
|
#endif
|
|
|
|
diff --git a/tests/runtests.c b/tests/runtests.c
|
|
index 40fdfb4..5234f49 100644
|
|
--- a/tests/runtests.c
|
|
+++ b/tests/runtests.c
|
|
@@ -61,7 +61,7 @@
|
|
#include "expat.h"
|
|
#include "chardata.h"
|
|
#include "structdata.h"
|
|
-#include "internal.h" /* for UNUSED_P only */
|
|
+#include "internal.h"
|
|
#include "minicheck.h"
|
|
#include "memcheck.h"
|
|
#include "siphash.h"
|
|
@@ -11225,6 +11225,296 @@ START_TEST(test_nsalloc_prefixed_element) {
|
|
}
|
|
END_TEST
|
|
|
|
+#if defined(XML_DTD)
|
|
+typedef enum XML_Status (*XmlParseFunction)(XML_Parser, const char *, int, int);
|
|
+
|
|
+struct AccountingTestCase {
|
|
+ const char *primaryText;
|
|
+ const char *firstExternalText; /* often NULL */
|
|
+ const char *secondExternalText; /* often NULL */
|
|
+ const unsigned long long expectedCountBytesIndirectExtra;
|
|
+ XML_Bool singleBytesWanted;
|
|
+};
|
|
+
|
|
+static int
|
|
+accounting_external_entity_ref_handler(XML_Parser parser,
|
|
+ const XML_Char *context,
|
|
+ const XML_Char *base,
|
|
+ const XML_Char *systemId,
|
|
+ const XML_Char *publicId) {
|
|
+ UNUSED_P(context);
|
|
+ UNUSED_P(base);
|
|
+ UNUSED_P(publicId);
|
|
+
|
|
+ const struct AccountingTestCase *const testCase
|
|
+ = (const struct AccountingTestCase *)XML_GetUserData(parser);
|
|
+
|
|
+ const char *externalText = NULL;
|
|
+ if (xcstrcmp(systemId, XCS("first.ent")) == 0) {
|
|
+ externalText = testCase->firstExternalText;
|
|
+ } else if (xcstrcmp(systemId, XCS("second.ent")) == 0) {
|
|
+ externalText = testCase->secondExternalText;
|
|
+ } else {
|
|
+ assert(! "systemId is neither \"first.ent\" nor \"second.ent\"");
|
|
+ }
|
|
+ assert(externalText);
|
|
+
|
|
+ XML_Parser entParser = XML_ExternalEntityParserCreate(parser, context, 0);
|
|
+ assert(entParser);
|
|
+
|
|
+ const XmlParseFunction xmlParseFunction
|
|
+ = testCase->singleBytesWanted ? _XML_Parse_SINGLE_BYTES : XML_Parse;
|
|
+
|
|
+ const enum XML_Status status = xmlParseFunction(
|
|
+ entParser, externalText, (int)strlen(externalText), XML_TRUE);
|
|
+
|
|
+ XML_ParserFree(entParser);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+START_TEST(test_accounting_precision) {
|
|
+ const XML_Bool filled_later = XML_TRUE; /* value is arbitrary */
|
|
+ struct AccountingTestCase cases[] = {
|
|
+ {"<e/>", NULL, NULL, 0, 0},
|
|
+ {"<e></e>", NULL, NULL, 0, 0},
|
|
+
|
|
+ /* Attributes */
|
|
+ {"<e k1=\"v2\" k2=\"v2\"/>", NULL, NULL, 0, filled_later},
|
|
+ {"<e k1=\"v2\" k2=\"v2\"></e>", NULL, NULL, 0, 0},
|
|
+ {"<p:e xmlns:p=\"https://domain.invalid/\" />", NULL, NULL, 0,
|
|
+ filled_later},
|
|
+ {"<e k=\"&'><"\" />", NULL, NULL,
|
|
+ sizeof(XML_Char) * 5 /* number of predefined entites */, filled_later},
|
|
+ {"<e1 xmlns='https://example.org/'>\n"
|
|
+ " <e2 xmlns=''/>\n"
|
|
+ "</e1>",
|
|
+ NULL, NULL, 0, filled_later},
|
|
+
|
|
+ /* Text */
|
|
+ {"<e>text</e>", NULL, NULL, 0, filled_later},
|
|
+ {"<e1><e2>text1<e3/>text2</e2></e1>", NULL, NULL, 0, filled_later},
|
|
+ {"<e>&'><"</e>", NULL, NULL,
|
|
+ sizeof(XML_Char) * 5 /* number of predefined entites */, filled_later},
|
|
+ {"<e>A)</e>", NULL, NULL, 0, filled_later},
|
|
+
|
|
+ /* Prolog */
|
|
+ {"<?xml version=\"1.0\"?><root/>", NULL, NULL, 0, filled_later},
|
|
+
|
|
+ /* Whitespace */
|
|
+ {" <e1> <e2> </e2> </e1> ", NULL, NULL, 0, filled_later},
|
|
+ {"<e1 ><e2 /></e1 >", NULL, NULL, 0, filled_later},
|
|
+ {"<e1><e2 k = \"v\"/><e3 k = 'v'/></e1>", NULL, NULL, 0, filled_later},
|
|
+
|
|
+ /* Comments */
|
|
+ {"<!-- Comment --><e><!-- Comment --></e>", NULL, NULL, 0, filled_later},
|
|
+
|
|
+ /* Processing instructions */
|
|
+ {"<?xml-stylesheet type=\"text/xsl\" href=\"https://domain.invalid/\" media=\"all\"?><e/>",
|
|
+ NULL, NULL, 0, filled_later},
|
|
+ {"<?pi0?><?pi1 ?><?pi2 ?><!DOCTYPE r SYSTEM 'first.ent'><r/>",
|
|
+ "<?pi3?><!ENTITY % e1 SYSTEM 'second.ent'><?pi4?>%e1;<?pi5?>", "<?pi6?>",
|
|
+ 0, filled_later},
|
|
+
|
|
+ /* CDATA */
|
|
+ {"<e><![CDATA[one two three]]></e>", NULL, NULL, 0, filled_later},
|
|
+
|
|
+ /* Conditional sections */
|
|
+ {"<!DOCTYPE r [\n"
|
|
+ "<!ENTITY % draft 'INCLUDE'>\n"
|
|
+ "<!ENTITY % final 'IGNORE'>\n"
|
|
+ "<!ENTITY % import SYSTEM \"first.ent\">\n"
|
|
+ "%import;\n"
|
|
+ "]>\n"
|
|
+ "<r/>\n",
|
|
+ "<![%draft;[<!--1-->]]>\n"
|
|
+ "<![%final;[<!--22-->]]>",
|
|
+ NULL, sizeof(XML_Char) * (strlen("INCLUDE") + strlen("IGNORE")),
|
|
+ filled_later},
|
|
+
|
|
+ /* General entities */
|
|
+ {"<!DOCTYPE root [\n"
|
|
+ "<!ENTITY nine \"123456789\">\n"
|
|
+ "]>\n"
|
|
+ "<root>&nine;</root>",
|
|
+ NULL, NULL, sizeof(XML_Char) * strlen("123456789"), filled_later},
|
|
+ {"<!DOCTYPE root [\n"
|
|
+ "<!ENTITY nine \"123456789\">\n"
|
|
+ "]>\n"
|
|
+ "<root k1=\"&nine;\"/>",
|
|
+ NULL, NULL, sizeof(XML_Char) * strlen("123456789"), filled_later},
|
|
+ {"<!DOCTYPE root [\n"
|
|
+ "<!ENTITY nine \"123456789\">\n"
|
|
+ "<!ENTITY nine2 \"&nine;&nine;\">\n"
|
|
+ "]>\n"
|
|
+ "<root>&nine2;&nine2;&nine2;</root>",
|
|
+ NULL, NULL,
|
|
+ sizeof(XML_Char) * 3 /* calls to &nine2; */ * 2 /* calls to &nine; */
|
|
+ * (strlen("&nine;") + strlen("123456789")),
|
|
+ filled_later},
|
|
+ {"<!DOCTYPE r [\n"
|
|
+ " <!ENTITY five SYSTEM 'first.ent'>\n"
|
|
+ "]>\n"
|
|
+ "<r>&five;</r>",
|
|
+ "12345", NULL, 0, filled_later},
|
|
+
|
|
+ /* Parameter entities */
|
|
+ {"<!DOCTYPE r [\n"
|
|
+ "<!ENTITY % comment \"<!---->\">\n"
|
|
+ "%comment;\n"
|
|
+ "]>\n"
|
|
+ "<r/>",
|
|
+ NULL, NULL, sizeof(XML_Char) * strlen("<!---->"), filled_later},
|
|
+ {"<!DOCTYPE r [\n"
|
|
+ "<!ENTITY % ninedef \"<!ENTITY nine "123456789">\">\n"
|
|
+ "%ninedef;\n"
|
|
+ "]>\n"
|
|
+ "<r>&nine;</r>",
|
|
+ NULL, NULL,
|
|
+ sizeof(XML_Char)
|
|
+ * (strlen("<!ENTITY nine \"123456789\">") + strlen("123456789")),
|
|
+ filled_later},
|
|
+ {"<!DOCTYPE r [\n"
|
|
+ "<!ENTITY % comment \"<!--1-->\">\n"
|
|
+ "<!ENTITY % comment2 \"%comment;<!--22-->%comment;\">\n"
|
|
+ "%comment2;\n"
|
|
+ "]>\n"
|
|
+ "<r/>\n",
|
|
+ NULL, NULL,
|
|
+ sizeof(XML_Char)
|
|
+ * (strlen("%comment;<!--22-->%comment;") + 2 * strlen("<!--1-->")),
|
|
+ filled_later},
|
|
+ {"<!DOCTYPE r [\n"
|
|
+ " <!ENTITY % five \"12345\">\n"
|
|
+ " <!ENTITY % five2def \"<!ENTITY five2 "[%five;][%five;]]]]">\">\n"
|
|
+ " %five2def;\n"
|
|
+ "]>\n"
|
|
+ "<r>&five2;</r>",
|
|
+ NULL, NULL, /* from "%five2def;": */
|
|
+ sizeof(XML_Char)
|
|
+ * (strlen("<!ENTITY five2 \"[%five;][%five;]]]]\">")
|
|
+ + 2 /* calls to "%five;" */ * strlen("12345")
|
|
+ + /* from "&five2;": */ strlen("[12345][12345]]]]")),
|
|
+ filled_later},
|
|
+ {"<!DOCTYPE r SYSTEM \"first.ent\">\n"
|
|
+ "<r/>",
|
|
+ "<!ENTITY % comment '<!--1-->'>\n"
|
|
+ "<!ENTITY % comment2 '<!--22-->%comment;<!--22-->%comment;<!--22-->'>\n"
|
|
+ "%comment2;",
|
|
+ NULL,
|
|
+ sizeof(XML_Char)
|
|
+ * (strlen("<!--22-->%comment;<!--22-->%comment;<!--22-->")
|
|
+ + 2 /* calls to "%comment;" */ * strlen("<!---->")),
|
|
+ filled_later},
|
|
+ {"<!DOCTYPE r SYSTEM 'first.ent'>\n"
|
|
+ "<r/>",
|
|
+ "<!ENTITY % e1 PUBLIC 'foo' 'second.ent'>\n"
|
|
+ "<!ENTITY % e2 '<!--22-->%e1;<!--22-->'>\n"
|
|
+ "%e2;\n",
|
|
+ "<!--1-->", sizeof(XML_Char) * strlen("<!--22--><!--1--><!--22-->"),
|
|
+ filled_later},
|
|
+ {
|
|
+ "<!DOCTYPE r SYSTEM 'first.ent'>\n"
|
|
+ "<r/>",
|
|
+ "<!ENTITY % e1 SYSTEM 'second.ent'>\n"
|
|
+ "<!ENTITY % e2 '%e1;'>",
|
|
+ "<?xml version='1.0' encoding='utf-8'?>\n"
|
|
+ "hello\n"
|
|
+ "xml" /* without trailing newline! */,
|
|
+ 0,
|
|
+ filled_later,
|
|
+ },
|
|
+ {
|
|
+ "<!DOCTYPE r SYSTEM 'first.ent'>\n"
|
|
+ "<r/>",
|
|
+ "<!ENTITY % e1 SYSTEM 'second.ent'>\n"
|
|
+ "<!ENTITY % e2 '%e1;'>",
|
|
+ "<?xml version='1.0' encoding='utf-8'?>\n"
|
|
+ "hello\n"
|
|
+ "xml\n" /* with trailing newline! */,
|
|
+ 0,
|
|
+ filled_later,
|
|
+ },
|
|
+ {"<!DOCTYPE doc SYSTEM 'first.ent'>\n"
|
|
+ "<doc></doc>\n",
|
|
+ "<!ELEMENT doc EMPTY>\n"
|
|
+ "<!ENTITY % e1 SYSTEM 'second.ent'>\n"
|
|
+ "<!ENTITY % e2 '%e1;'>\n"
|
|
+ "%e1;\n",
|
|
+ "\xEF\xBB\xBF<!ATTLIST doc a1 CDATA 'value'>" /* UTF-8 BOM */,
|
|
+ strlen("\xEF\xBB\xBF<!ATTLIST doc a1 CDATA 'value'>"), filled_later},
|
|
+ {"<!DOCTYPE r [\n"
|
|
+ " <!ENTITY five SYSTEM 'first.ent'>\n"
|
|
+ "]>\n"
|
|
+ "<r>&five;</r>",
|
|
+ "\xEF\xBB\xBF" /* UTF-8 BOM */, NULL, 0, filled_later},
|
|
+ };
|
|
+
|
|
+ const size_t countCases = sizeof(cases) / sizeof(cases[0]);
|
|
+ size_t u = 0;
|
|
+ for (; u < countCases; u++) {
|
|
+ size_t v = 0;
|
|
+ for (; v < 2; v++) {
|
|
+ const XML_Bool singleBytesWanted = (v == 0) ? XML_FALSE : XML_TRUE;
|
|
+ const unsigned long long expectedCountBytesDirect
|
|
+ = strlen(cases[u].primaryText);
|
|
+ const unsigned long long expectedCountBytesIndirect
|
|
+ = (cases[u].firstExternalText ? strlen(cases[u].firstExternalText)
|
|
+ : 0)
|
|
+ + (cases[u].secondExternalText ? strlen(cases[u].secondExternalText)
|
|
+ : 0)
|
|
+ + cases[u].expectedCountBytesIndirectExtra;
|
|
+
|
|
+ XML_Parser parser = XML_ParserCreate(NULL);
|
|
+ XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
|
|
+ if (cases[u].firstExternalText) {
|
|
+ XML_SetExternalEntityRefHandler(parser,
|
|
+ accounting_external_entity_ref_handler);
|
|
+ XML_SetUserData(parser, (void *)&cases[u]);
|
|
+ cases[u].singleBytesWanted = singleBytesWanted;
|
|
+ }
|
|
+
|
|
+ const XmlParseFunction xmlParseFunction
|
|
+ = singleBytesWanted ? _XML_Parse_SINGLE_BYTES : XML_Parse;
|
|
+
|
|
+ enum XML_Status status
|
|
+ = xmlParseFunction(parser, cases[u].primaryText,
|
|
+ (int)strlen(cases[u].primaryText), XML_TRUE);
|
|
+ if (status != XML_STATUS_OK) {
|
|
+ _xml_failure(parser, __FILE__, __LINE__);
|
|
+ }
|
|
+
|
|
+ const unsigned long long actualCountBytesDirect
|
|
+ = testingAccountingGetCountBytesDirect(parser);
|
|
+ const unsigned long long actualCountBytesIndirect
|
|
+ = testingAccountingGetCountBytesIndirect(parser);
|
|
+
|
|
+ XML_ParserFree(parser);
|
|
+
|
|
+ if (actualCountBytesDirect != expectedCountBytesDirect) {
|
|
+ fprintf(
|
|
+ stderr,
|
|
+ "Document " EXPAT_FMT_SIZE_T("") " of " EXPAT_FMT_SIZE_T("") ", %s: Expected " EXPAT_FMT_ULL(
|
|
+ "") " count direct bytes, got " EXPAT_FMT_ULL("") " instead.\n",
|
|
+ u + 1, countCases, singleBytesWanted ? "single bytes" : "chunks",
|
|
+ expectedCountBytesDirect, actualCountBytesDirect);
|
|
+ fail("Count of direct bytes is off");
|
|
+ }
|
|
+
|
|
+ if (actualCountBytesIndirect != expectedCountBytesIndirect) {
|
|
+ fprintf(
|
|
+ stderr,
|
|
+ "Document " EXPAT_FMT_SIZE_T("") " of " EXPAT_FMT_SIZE_T("") ", %s: Expected " EXPAT_FMT_ULL(
|
|
+ "") " count indirect bytes, got " EXPAT_FMT_ULL("") " instead.\n",
|
|
+ u + 1, countCases, singleBytesWanted ? "single bytes" : "chunks",
|
|
+ expectedCountBytesIndirect, actualCountBytesIndirect);
|
|
+ fail("Count of indirect bytes is off");
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
+END_TEST
|
|
+#endif // defined(XML_DTD)
|
|
+
|
|
static Suite *
|
|
make_suite(void) {
|
|
Suite *s = suite_create("basic");
|
|
@@ -11233,6 +11523,9 @@ make_suite(void) {
|
|
TCase *tc_misc = tcase_create("miscellaneous tests");
|
|
TCase *tc_alloc = tcase_create("allocation tests");
|
|
TCase *tc_nsalloc = tcase_create("namespace allocation tests");
|
|
+#if defined(XML_DTD)
|
|
+ TCase *tc_accounting = tcase_create("accounting tests");
|
|
+#endif
|
|
|
|
suite_add_tcase(s, tc_basic);
|
|
tcase_add_checked_fixture(tc_basic, basic_setup, basic_teardown);
|
|
@@ -11593,6 +11886,11 @@ make_suite(void) {
|
|
tcase_add_test(tc_nsalloc, test_nsalloc_long_systemid_in_ext);
|
|
tcase_add_test(tc_nsalloc, test_nsalloc_prefixed_element);
|
|
|
|
+#if defined(XML_DTD)
|
|
+ suite_add_tcase(s, tc_accounting);
|
|
+ tcase_add_test(tc_accounting, test_accounting_precision);
|
|
+#endif
|
|
+
|
|
return s;
|
|
}
|
|
|
|
--
|
|
1.8.3.1
|
|
|