xref: /freebsd/contrib/expat/tests/misc_tests.c (revision 22c3edb31297a79ec3c7bf8f906be2df11b472d2)
1 /* Tests in the "miscellaneous" test case for the Expat test suite
2                             __  __            _
3                          ___\ \/ /_ __   __ _| |_
4                         / _ \\  /| '_ \ / _` | __|
5                        |  __//  \| |_) | (_| | |_
6                         \___/_/\_\ .__/ \__,_|\__|
7                                  |_| XML parser
8 
9    Copyright (c) 2001-2006 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
10    Copyright (c) 2003      Greg Stein <gstein@users.sourceforge.net>
11    Copyright (c) 2005-2007 Steven Solie <steven@solie.ca>
12    Copyright (c) 2005-2012 Karl Waclawek <karl@waclawek.net>
13    Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org>
14    Copyright (c) 2017-2022 Rhodri James <rhodri@wildebeest.org.uk>
15    Copyright (c) 2017      Joe Orton <jorton@redhat.com>
16    Copyright (c) 2017      José Gutiérrez de la Concha <jose@zeroc.com>
17    Copyright (c) 2018      Marco Maggi <marco.maggi-ipsu@poste.it>
18    Copyright (c) 2019      David Loffredo <loffredo@steptools.com>
19    Copyright (c) 2020      Tim Gates <tim.gates@iress.com>
20    Copyright (c) 2021      Donghee Na <donghee.na@python.org>
21    Copyright (c) 2023      Sony Corporation / Snild Dolkow <snild@sony.com>
22    Copyright (c) 2025      Berkay Eren Ürün <berkay.ueruen@siemens.com>
23    Copyright (c) 2026      Matthew Fernandez <matthew.fernandez@gmail.com>
24    Copyright (c) 2026      Kartik Kenchi <netliomax25@gmail.com>
25    Copyright (c) 2026      Evgeny Kotkov <kotkov@apache.org>
26    Copyright (c) 2026      Darren Carreras <carrerasdarren@gmail.com>
27    Copyright (c) 2026      Junki Lee <junkilee80@gmail.com>
28    Licensed under the MIT license:
29 
30    Permission is  hereby granted,  free of charge,  to any  person obtaining
31    a  copy  of  this  software   and  associated  documentation  files  (the
32    "Software"),  to  deal in  the  Software  without restriction,  including
33    without  limitation the  rights  to use,  copy,  modify, merge,  publish,
34    distribute, sublicense, and/or sell copies of the Software, and to permit
35    persons  to whom  the Software  is  furnished to  do so,  subject to  the
36    following conditions:
37 
38    The above copyright  notice and this permission notice  shall be included
39    in all copies or substantial portions of the Software.
40 
41    THE  SOFTWARE  IS  PROVIDED  "AS  IS",  WITHOUT  WARRANTY  OF  ANY  KIND,
42    EXPRESS  OR IMPLIED,  INCLUDING  BUT  NOT LIMITED  TO  THE WARRANTIES  OF
43    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
44    NO EVENT SHALL THE AUTHORS OR  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
45    DAMAGES OR  OTHER LIABILITY, WHETHER  IN AN  ACTION OF CONTRACT,  TORT OR
46    OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
47    USE OR OTHER DEALINGS IN THE SOFTWARE.
48 
49    SPDX-License-Identifier: MIT
50 */
51 
52 #if defined(NDEBUG)
53 #  undef NDEBUG /* because test suite relies on assert(...) at the moment */
54 #endif
55 
56 #include "expat_config.h"
57 
58 #include <assert.h>
59 #include <stdbool.h>
60 #include <stdint.h>
61 #include <string.h>
62 
63 #include "expat.h"
64 #include "internal.h"
65 #include "minicheck.h"
66 #include "memcheck.h"
67 #include "common.h"
68 #include "ascii.h" /* for ASCII_xxx */
69 #include "handlers.h"
70 #include "misc_tests.h"
71 #include "../lib/xcs.h"
72 
73 void XMLCALL accumulate_characters_ext_handler(void *userData,
74                                                const XML_Char *s, int len);
75 
76 /* Test that a failure to allocate the parser structure fails gracefully */
77 START_TEST(test_misc_alloc_create_parser) {
78   XML_Memory_Handling_Suite memsuite = {duff_allocator, realloc, free};
79   unsigned int i;
80   const unsigned int max_alloc_count = 10;
81 
82   /* Something this simple shouldn't need more than 10 allocations */
83   for (i = 0; i < max_alloc_count; i++) {
84     g_allocation_count = (int)i;
85     g_parser = XML_ParserCreate_MM(NULL, &memsuite, NULL);
86     if (g_parser != NULL)
87       break;
88   }
89   if (i == 0)
90     fail("Parser unexpectedly ignored failing allocator");
91   else if (i == max_alloc_count)
92     fail("Parser not created with max allocation count");
93 }
94 END_TEST
95 
96 /* Test memory allocation failures for a parser with an encoding */
97 START_TEST(test_misc_alloc_create_parser_with_encoding) {
98   XML_Memory_Handling_Suite memsuite = {duff_allocator, realloc, free};
99   unsigned int i;
100   const unsigned int max_alloc_count = 10;
101 
102   /* Try several levels of allocation */
103   for (i = 0; i < max_alloc_count; i++) {
104     g_allocation_count = (int)i;
105     g_parser = XML_ParserCreate_MM(XCS("us-ascii"), &memsuite, NULL);
106     if (g_parser != NULL)
107       break;
108   }
109   if (i == 0)
110     fail("Parser ignored failing allocator");
111   else if (i == max_alloc_count)
112     fail("Parser not created with max allocation count");
113 }
114 END_TEST
115 
116 /* Test that freeing a NULL parser doesn't cause an explosion.
117  * (Not actually tested anywhere else)
118  */
119 START_TEST(test_misc_null_parser) {
120   XML_ParserFree(NULL);
121 }
122 END_TEST
123 
124 #if defined(__has_feature)
125 #  if __has_feature(undefined_behavior_sanitizer)
126 #    define EXPAT_TESTS_UBSAN 1
127 #  else
128 #    define EXPAT_TESTS_UBSAN 0
129 #  endif
130 #else
131 #  define EXPAT_TESTS_UBSAN 0
132 #endif
133 
134 /* Test that XML_ErrorString rejects out-of-range codes */
135 START_TEST(test_misc_error_string) {
136 #if ! EXPAT_TESTS_UBSAN // because this would trigger UBSan
137   union {
138     enum XML_Error xml_error;
139     int integer;
140   } trickery;
141 
142   assert_true(sizeof(enum XML_Error) == sizeof(int)); // self-test
143 
144   trickery.integer = -1;
145   if (XML_ErrorString(trickery.xml_error) != NULL)
146     fail("Negative error code not rejected");
147 
148   trickery.integer = 100;
149   if (XML_ErrorString(trickery.xml_error) != NULL)
150     fail("Large error code not rejected");
151 #endif
152 }
153 END_TEST
154 
155 /* Test the version information is consistent */
156 
157 /* Since we are working in XML_LChars (potentially 16-bits), we
158  * can't use the standard C library functions for character
159  * manipulation and have to roll our own.
160  */
161 static int
162 parse_version(const XML_LChar *version_text,
163               XML_Expat_Version *version_struct) {
164   if (! version_text)
165     return XML_FALSE;
166 
167   while (*version_text != 0x00) {
168     if (*version_text >= ASCII_0 && *version_text <= ASCII_9)
169       break;
170     version_text++;
171   }
172   if (*version_text == 0x00)
173     return XML_FALSE;
174 
175   /* version_struct->major = strtoul(version_text, 10, &version_text) */
176   version_struct->major = 0;
177   while (*version_text >= ASCII_0 && *version_text <= ASCII_9) {
178     version_struct->major
179         = 10 * version_struct->major + (*version_text++ - ASCII_0);
180   }
181   if (*version_text++ != ASCII_PERIOD)
182     return XML_FALSE;
183 
184   /* Now for the minor version number */
185   version_struct->minor = 0;
186   while (*version_text >= ASCII_0 && *version_text <= ASCII_9) {
187     version_struct->minor
188         = 10 * version_struct->minor + (*version_text++ - ASCII_0);
189   }
190   if (*version_text++ != ASCII_PERIOD)
191     return XML_FALSE;
192 
193   /* Finally the micro version number */
194   version_struct->micro = 0;
195   while (*version_text >= ASCII_0 && *version_text <= ASCII_9) {
196     version_struct->micro
197         = 10 * version_struct->micro + (*version_text++ - ASCII_0);
198   }
199   if (*version_text != 0x00)
200     return XML_FALSE;
201   return XML_TRUE;
202 }
203 
204 static int
205 versions_equal(const XML_Expat_Version *first,
206                const XML_Expat_Version *second) {
207   return (first->major == second->major && first->minor == second->minor
208           && first->micro == second->micro);
209 }
210 
211 START_TEST(test_misc_version) {
212   XML_Expat_Version read_version = XML_ExpatVersionInfo();
213   /* Silence compiler warning with the following assignment */
214   XML_Expat_Version parsed_version = {0, 0, 0};
215   const XML_LChar *version_text = XML_ExpatVersion();
216 
217   if (version_text == NULL)
218     fail("Could not obtain version text");
219   assert(version_text != NULL);
220   if (! parse_version(version_text, &parsed_version))
221     fail("Unable to parse version text");
222   if (! versions_equal(&read_version, &parsed_version))
223     fail("Version mismatch");
224 
225   if (xcscmp(version_text, XCS("expat_2.9.0"))
226       != 0) /* needs bump on releases */
227     fail("XML_*_VERSION in expat.h out of sync?\n");
228 }
229 END_TEST
230 
231 /* Test feature information */
232 START_TEST(test_misc_features) {
233   const XML_Feature *features = XML_GetFeatureList();
234 
235   /* Prevent problems with double-freeing parsers */
236   g_parser = NULL;
237   if (features == NULL) {
238     fail("Failed to get feature information");
239   } else {
240     /* Loop through the features checking what we can */
241     while (features->feature != XML_FEATURE_END) {
242       switch (features->feature) {
243       case XML_FEATURE_SIZEOF_XML_CHAR:
244         if (features->value != sizeof(XML_Char))
245           fail("Incorrect size of XML_Char");
246         break;
247       case XML_FEATURE_SIZEOF_XML_LCHAR:
248         if (features->value != sizeof(XML_LChar))
249           fail("Incorrect size of XML_LChar");
250         break;
251       default:
252         break;
253       }
254       features++;
255     }
256   }
257 }
258 END_TEST
259 
260 /* Regression test for GitHub Issue #17: memory leak parsing attribute
261  * values with mixed bound and unbound namespaces.
262  */
263 START_TEST(test_misc_attribute_leak) {
264   const char *text = "<D xmlns:L=\"D\" l:a='' L:a=''/>";
265   XML_Memory_Handling_Suite memsuite
266       = {tracking_malloc, tracking_realloc, tracking_free};
267 
268   g_parser = XML_ParserCreate_MM(XCS("UTF-8"), &memsuite, XCS("\n"));
269   expect_failure(text, XML_ERROR_UNBOUND_PREFIX, "Unbound prefixes not found");
270   XML_ParserFree(g_parser);
271   /* Prevent the teardown trying to double free */
272   g_parser = NULL;
273 
274   if (! tracking_report())
275     fail("Memory leak found");
276 }
277 END_TEST
278 
279 /* Test parser created for UTF-16LE is successful */
280 START_TEST(test_misc_utf16le) {
281   const char text[] =
282       /* <?xml version='1.0'?><q>Hi</q> */
283       "<\0?\0x\0m\0l\0 \0"
284       "v\0e\0r\0s\0i\0o\0n\0=\0'\0\x31\0.\0\x30\0'\0?\0>\0"
285       "<\0q\0>\0H\0i\0<\0/\0q\0>\0";
286   const XML_Char *expected = XCS("Hi");
287   CharData storage;
288 
289   g_parser = XML_ParserCreate(XCS("UTF-16LE"));
290   if (g_parser == NULL)
291     fail("Parser not created");
292 
293   CharData_Init(&storage);
294   XML_SetUserData(g_parser, &storage);
295   XML_SetCharacterDataHandler(g_parser, accumulate_characters);
296   if (_XML_Parse_SINGLE_BYTES(g_parser, text, (int)sizeof(text) - 1, XML_TRUE)
297       == XML_STATUS_ERROR)
298     xml_failure(g_parser);
299   CharData_CheckXMLChars(&storage, expected);
300 }
301 END_TEST
302 
303 START_TEST(test_misc_stop_during_end_handler_issue_240_1) {
304   XML_Parser parser;
305   DataIssue240 *mydata;
306   enum XML_Status result;
307   const char *const doc1 = "<doc><e1/><e><foo/></e></doc>";
308 
309   parser = XML_ParserCreate(NULL);
310   XML_SetElementHandler(parser, start_element_issue_240, end_element_issue_240);
311   mydata = malloc(sizeof(DataIssue240));
312   assert_true(mydata != NULL);
313   mydata->parser = parser;
314   mydata->deep = 0;
315   XML_SetUserData(parser, mydata);
316 
317   result = _XML_Parse_SINGLE_BYTES(parser, doc1, (int)strlen(doc1), 1);
318   XML_ParserFree(parser);
319   free(mydata);
320   if (result != XML_STATUS_ERROR)
321     fail("Stopping the parser did not work as expected");
322 }
323 END_TEST
324 
325 START_TEST(test_misc_stop_during_end_handler_issue_240_2) {
326   XML_Parser parser;
327   DataIssue240 *mydata;
328   enum XML_Status result;
329   const char *const doc2 = "<doc><elem/></doc>";
330 
331   parser = XML_ParserCreate(NULL);
332   XML_SetElementHandler(parser, start_element_issue_240, end_element_issue_240);
333   mydata = malloc(sizeof(DataIssue240));
334   assert_true(mydata != NULL);
335   mydata->parser = parser;
336   mydata->deep = 0;
337   XML_SetUserData(parser, mydata);
338 
339   result = _XML_Parse_SINGLE_BYTES(parser, doc2, (int)strlen(doc2), 1);
340   XML_ParserFree(parser);
341   free(mydata);
342   if (result != XML_STATUS_ERROR)
343     fail("Stopping the parser did not work as expected");
344 }
345 END_TEST
346 
347 START_TEST(test_misc_deny_internal_entity_closing_doctype_issue_317) {
348   const char *const inputOne
349       = "<!DOCTYPE d [\n"
350         "<!ENTITY % element_d '<!ELEMENT d (#PCDATA)*>'>\n"
351         "%element_d;\n"
352         "<!ENTITY % e ']><d/>'>\n"
353         "\n"
354         "%e;";
355   const char *const inputTwo
356       = "<!DOCTYPE d [\n"
357         "<!ENTITY % element_d '<!ELEMENT d (#PCDATA)*>'>\n"
358         "%element_d;\n"
359         "<!ENTITY % e1 ']><d/>'><!ENTITY % e2 '&#37;e1;'>\n"
360         "\n"
361         "%e2;";
362   const char *const inputThree
363       = "<!DOCTYPE d [\n"
364         "<!ENTITY % element_d '<!ELEMENT d (#PCDATA)*>'>\n"
365         "%element_d;\n"
366         "<!ENTITY % e ']><d'>\n"
367         "\n"
368         "%e;/>";
369   const char *const inputIssue317
370       = "<!DOCTYPE doc [\n"
371         "<!ENTITY % element_doc '<!ELEMENT doc (#PCDATA)*>'>\n"
372         "%element_doc;\n"
373         "<!ENTITY % foo ']>\n"
374         "<doc>Hell<oc (#PCDATA)*>'>\n"
375         "%foo;\n"
376         "]>\n"
377         "<doc>Hello, world</dVc>";
378 
379   const char *const inputs[] = {inputOne, inputTwo, inputThree, inputIssue317};
380   const XML_Bool suspendOrNot[] = {XML_FALSE, XML_TRUE};
381   size_t inputIndex = 0;
382 
383   for (; inputIndex < sizeof(inputs) / sizeof(inputs[0]); inputIndex++) {
384     for (size_t suspendOrNotIndex = 0;
385          suspendOrNotIndex < sizeof(suspendOrNot) / sizeof(suspendOrNot[0]);
386          suspendOrNotIndex++) {
387       const char *const input = inputs[inputIndex];
388       const XML_Bool suspend = suspendOrNot[suspendOrNotIndex];
389       if (suspend && (g_chunkSize > 0)) {
390         // We cannot use _XML_Parse_SINGLE_BYTES below due to suspension, and
391         // so chunk sizes >0 would only repeat the very same test
392         // due to use of plain XML_Parse; we are saving upon that runtime:
393         return;
394       }
395 
396       set_subtest("[input=%d suspend=%s] %s", (int)inputIndex,
397                   suspend ? "true" : "false", input);
398       XML_Parser parser;
399       enum XML_Status parseResult;
400       int setParamEntityResult;
401       uint64_t lineNumber;
402       uint64_t columnNumber;
403 
404       parser = XML_ParserCreate(NULL);
405       setParamEntityResult
406           = XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
407       if (setParamEntityResult != 1)
408         fail("Failed to set XML_PARAM_ENTITY_PARSING_ALWAYS.");
409 
410       if (suspend) {
411         XML_SetUserData(parser, parser);
412         XML_SetElementDeclHandler(parser, suspend_after_element_declaration);
413       }
414 
415       if (suspend) {
416         // can't use SINGLE_BYTES here, because it'll return early on
417         // suspension, and we won't know exactly how much input we actually
418         // managed to give Expat.
419         parseResult = XML_Parse(parser, input, (int)strlen(input), 0);
420 
421         while (parseResult == XML_STATUS_SUSPENDED) {
422           parseResult = XML_ResumeParser(parser);
423         }
424 
425         if (parseResult != XML_STATUS_ERROR) {
426           // can't use SINGLE_BYTES here, because it'll return early on
427           // suspension, and we won't know exactly how much input we actually
428           // managed to give Expat.
429           parseResult = XML_Parse(parser, "", 0, 1);
430         }
431 
432         while (parseResult == XML_STATUS_SUSPENDED) {
433           parseResult = XML_ResumeParser(parser);
434         }
435       } else {
436         parseResult
437             = _XML_Parse_SINGLE_BYTES(parser, input, (int)strlen(input), 0);
438 
439         if (parseResult != XML_STATUS_ERROR) {
440           parseResult = _XML_Parse_SINGLE_BYTES(parser, "", 0, 1);
441         }
442       }
443 
444       if (parseResult != XML_STATUS_ERROR) {
445         fail("Parsing was expected to fail but succeeded.");
446       }
447 
448       if (XML_GetErrorCode(parser) != XML_ERROR_INVALID_TOKEN)
449         fail("Error code does not match XML_ERROR_INVALID_TOKEN");
450 
451       lineNumber = XML_GetCurrentLineNumber64(parser);
452       if (lineNumber != 6)
453         fail("XML_GetCurrentLineNumber64 does not work as expected.");
454 
455       columnNumber = XML_GetCurrentColumnNumber64(parser);
456       if (columnNumber != 0)
457         fail("XML_GetCurrentColumnNumber64 does not work as expected.");
458 
459       XML_ParserFree(parser);
460     }
461   }
462 }
463 END_TEST
464 
465 START_TEST(test_misc_tag_mismatch_reset_leak) {
466 #ifdef XML_NS
467   const char *const text = "<open xmlns='https://namespace1.test'></close>";
468   XML_Parser parser = XML_ParserCreateNS(NULL, XCS('\n'));
469 
470   if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
471       != XML_STATUS_ERROR)
472     fail("Call to parse was expected to fail");
473   if (XML_GetErrorCode(parser) != XML_ERROR_TAG_MISMATCH)
474     fail("Call to parse was expected to fail from a closing tag mismatch");
475 
476   XML_ParserReset(parser, NULL);
477 
478   if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
479       != XML_STATUS_ERROR)
480     fail("Call to parse was expected to fail");
481   if (XML_GetErrorCode(parser) != XML_ERROR_TAG_MISMATCH)
482     fail("Call to parse was expected to fail from a closing tag mismatch");
483 
484   XML_ParserFree(parser);
485 #endif
486 }
487 END_TEST
488 
489 START_TEST(test_misc_create_external_entity_parser_with_null_context) {
490   // With XML_DTD undefined, the only supported case of external entities
491   // is pattern "<!ENTITY entity123 SYSTEM 'filename123'>". A NULL context
492   // was causing a segfault through a null pointer dereference in function
493   // setContext, previously.
494   XML_Parser parser = XML_ParserCreate(NULL);
495   XML_Parser ext_parser = XML_ExternalEntityParserCreate(parser, NULL, NULL);
496 #ifdef XML_DTD
497   assert_true(ext_parser != NULL);
498   XML_ParserFree(ext_parser);
499 #else
500   assert_true(ext_parser == NULL);
501 #endif /* XML_DTD */
502   XML_ParserFree(parser);
503 }
504 END_TEST
505 
506 START_TEST(test_misc_general_entities_support) {
507   const char *const doc
508       = "<!DOCTYPE r [\n"
509         "<!ENTITY e1 'v1'>\n"
510         "<!ENTITY e2 SYSTEM 'v2'>\n"
511         "]>\n"
512         "<r a1='[&e1;]'>[&e1;][&e2;][&amp;&apos;&gt;&lt;&quot;]</r>";
513 
514   CharData storage;
515   CharData_Init(&storage);
516 
517   XML_Parser parser = XML_ParserCreate(NULL);
518   XML_SetUserData(parser, &storage);
519   XML_SetStartElementHandler(parser, accumulate_start_element);
520   XML_SetExternalEntityRefHandler(parser,
521                                   external_entity_failer__if_not_xml_ge);
522   XML_SetEntityDeclHandler(parser, accumulate_entity_decl);
523   XML_SetCharacterDataHandler(parser, accumulate_characters);
524 
525   if (_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc), XML_TRUE)
526       != XML_STATUS_OK) {
527     xml_failure(parser);
528   }
529 
530   XML_ParserFree(parser);
531 
532   CharData_CheckXMLChars(&storage,
533   /* clang-format off */
534 #if XML_GE == 1
535                          XCS("e1=v1\n")
536                          XCS("e2=(null)\n")
537                          XCS("(r(a1=[v1]))\n")
538                          XCS("[v1][][&'><\"]")
539 #else
540                          XCS("e1=&amp;e1;\n")
541                          XCS("e2=(null)\n")
542                          XCS("(r(a1=[&e1;]))\n")
543                          XCS("[&e1;][&e2;][&'><\"]")
544 #endif
545   );
546   /* clang-format on */
547 }
548 END_TEST
549 
550 static void XMLCALL
551 resumable_stopping_character_handler(void *userData, const XML_Char *s,
552                                      int len) {
553   UNUSED_P(s);
554   UNUSED_P(len);
555   XML_Parser parser = (XML_Parser)userData;
556   XML_StopParser(parser, XML_TRUE);
557 }
558 
559 // NOTE: This test needs active LeakSanitizer to be of actual use
560 START_TEST(test_misc_char_handler_stop_without_leak) {
561   const char *const data
562       = "<!DOCTYPE t1[<!ENTITY e1 'angle<'><!ENTITY e2 '&e1;'>]><t1>&e2;";
563   XML_Parser parser = XML_ParserCreate(NULL);
564   assert_true(parser != NULL);
565   XML_SetUserData(parser, parser);
566   XML_SetCharacterDataHandler(parser, resumable_stopping_character_handler);
567   _XML_Parse_SINGLE_BYTES(parser, data, (int)strlen(data), XML_FALSE);
568   XML_ParserFree(parser);
569 }
570 END_TEST
571 
572 START_TEST(test_misc_resumeparser_not_crashing) {
573   XML_Parser parser = XML_ParserCreate(NULL);
574   XML_GetBuffer(parser, 1);
575   XML_StopParser(parser, /*resumable=*/XML_TRUE);
576   XML_ResumeParser(parser); // could crash here, previously
577   XML_ParserFree(parser);
578 }
579 END_TEST
580 
581 START_TEST(test_misc_stopparser_rejects_unstarted_parser) {
582   const XML_Bool cases[] = {XML_TRUE, XML_FALSE};
583   for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
584     const XML_Bool resumable = cases[i];
585     XML_Parser parser = XML_ParserCreate(NULL);
586     assert_true(XML_GetErrorCode(parser) == XML_ERROR_NONE);
587     assert_true(XML_StopParser(parser, resumable) == XML_STATUS_ERROR);
588     assert_true(XML_GetErrorCode(parser) == XML_ERROR_NOT_STARTED);
589     XML_ParserFree(parser);
590   }
591 }
592 END_TEST
593 
594 /* Adaptation of accumulate_characters that takes ExtHdlrData input to work with
595  * test_renter_loop_finite_content below */
596 void XMLCALL
597 accumulate_characters_ext_handler(void *userData, const XML_Char *s, int len) {
598   ExtHdlrData *const test_data = (ExtHdlrData *)userData;
599   CharData_AppendXMLChars(test_data->storage, s, len);
600 }
601 
602 /* Test that internalEntityProcessor does not re-enter forever;
603  * based on files tests/xmlconf/xmltest/valid/ext-sa/012.{xml,ent} */
604 START_TEST(test_renter_loop_finite_content) {
605   CharData storage;
606   CharData_Init(&storage);
607   const char *const text = "<!DOCTYPE doc [\n"
608                            "<!ENTITY e1 '&e2;'>\n"
609                            "<!ENTITY e2 '&e3;'>\n"
610                            "<!ENTITY e3 SYSTEM '012.ent'>\n"
611                            "<!ENTITY e4 '&e5;'>\n"
612                            "<!ENTITY e5 '(e5)'>\n"
613                            "<!ELEMENT doc (#PCDATA)>\n"
614                            "]>\n"
615                            "<doc>&e1;</doc>\n";
616   ExtHdlrData test_data = {"&e4;\n", external_entity_null_loader, &storage};
617   const XML_Char *const expected = XCS("(e5)\n");
618 
619   XML_Parser parser = XML_ParserCreate(NULL);
620   assert_true(parser != NULL);
621   XML_SetUserData(parser, &test_data);
622   XML_SetExternalEntityRefHandler(parser, external_entity_oneshot_loader);
623   XML_SetCharacterDataHandler(parser, accumulate_characters_ext_handler);
624   if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
625       == XML_STATUS_ERROR)
626     xml_failure(parser);
627 
628   CharData_CheckXMLChars(&storage, expected);
629   XML_ParserFree(parser);
630 }
631 END_TEST
632 
633 // Inspired by function XML_OriginalString of Perl's XML::Parser
634 static char *
635 dup_original_string(XML_Parser parser) {
636   const uint64_t byte_count = XML_GetCurrentByteCount64(parser);
637 
638   int64_t offset64 = -1;
639   uint64_t size64 = UINT64_MAX;
640 
641   const char *const context64
642       = XML_GetInputContext64(parser, &offset64, &size64);
643 
644 #if XML_CONTEXT_BYTES > 0
645   assert_true(context64 != NULL);
646   assert_true(offset64 != -1);
647   assert_true(size64 != UINT64_MAX);
648 
649   return portable_strndup(context64 + offset64, byte_count);
650 #else
651   UNUSED_P(byte_count);
652   assert_true(context64 == NULL);
653   return NULL;
654 #endif
655 }
656 
657 static void
658 on_characters_issue_980(void *userData, const XML_Char *s, int len) {
659   (void)s;
660   (void)len;
661   XML_Parser parser = (XML_Parser)userData;
662 
663   char *const original_string = dup_original_string(parser);
664 
665 #if XML_CONTEXT_BYTES > 0
666   assert_true(original_string != NULL);
667   assert_true(strcmp(original_string, "&draft.day;") == 0);
668   free(original_string);
669 #else
670   assert_true(original_string == NULL);
671 #endif
672 }
673 
674 START_TEST(test_misc_expected_event_ptr_issue_980) {
675   // NOTE: This is a tiny subset of sample "REC-xml-19980210.xml"
676   //       from Perl's XML::Parser
677   const char *const doc = "<!DOCTYPE day [\n"
678                           "  <!ENTITY draft.day '10'>\n"
679                           "]>\n"
680                           "<day>&draft.day;</day>\n";
681 
682   XML_Parser parser = XML_ParserCreate(NULL);
683   XML_SetUserData(parser, parser);
684   XML_SetCharacterDataHandler(parser, on_characters_issue_980);
685 
686   assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
687                                       /*isFinal=*/XML_TRUE)
688               == XML_STATUS_OK);
689 
690   XML_ParserFree(parser);
691 }
692 END_TEST
693 
694 START_TEST(test_misc_sync_entity_tolerated) {
695   const char *const doc = "<!DOCTYPE t0 [\n"
696                           "   <!ENTITY a '<t1></t1>'>\n"
697                           "   <!ENTITY b '<t2>two</t2>'>\n"
698                           "   <!ENTITY c '<t3>three<t4>four</t4>three</t3>'>\n"
699                           "   <!ENTITY d '<t5>&b;</t5>'>\n"
700                           "]>\n"
701                           "<t0>&a;&b;&c;&d;</t0>\n";
702   XML_Parser parser = XML_ParserCreate(NULL);
703 
704   assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
705                                       /*isFinal=*/XML_TRUE)
706               == XML_STATUS_OK);
707 
708   XML_ParserFree(parser);
709 }
710 END_TEST
711 
712 START_TEST(test_misc_async_entity_rejected) {
713   struct test_case {
714     const char *doc;
715     enum XML_Status expectedStatusNoGE;
716     enum XML_Error expectedErrorNoGE;
717     uint64_t expectedErrorLine;
718     uint16_t expectedErrorColumn;
719   };
720   const struct test_case cases[] = {
721       // Opened by one entity, closed by another
722       {"<!DOCTYPE t0 [\n"
723        "   <!ENTITY open '<t1>'>\n"
724        "   <!ENTITY close '</t1>'>\n"
725        "]>\n"
726        "<t0>&open;&close;</t0>\n",
727        XML_STATUS_OK, XML_ERROR_NONE, 5, 4},
728       // Opened by tag, closed by entity (non-root case)
729       {"<!DOCTYPE t0 [\n"
730        "  <!ENTITY g0 ''>\n"
731        "  <!ENTITY g1 '&g0;</t1>'>\n"
732        "]>\n"
733        "<t0><t1>&g1;</t0>\n",
734        XML_STATUS_ERROR, XML_ERROR_TAG_MISMATCH, 5, 8},
735       // Opened by tag, closed by entity (root case)
736       {"<!DOCTYPE t0 [\n"
737        "  <!ENTITY g0 ''>\n"
738        "  <!ENTITY g1 '&g0;</t0>'>\n"
739        "]>\n"
740        "<t0>&g1;\n",
741        XML_STATUS_ERROR, XML_ERROR_NO_ELEMENTS, 5, 4},
742       // Opened by entity, closed by tag <-- regression from 2.7.0
743       {"<!DOCTYPE t0 [\n"
744        "  <!ENTITY g0 ''>\n"
745        "  <!ENTITY g1 '<t1>&g0;'>\n"
746        "]>\n"
747        "<t0>&g1;</t1></t0>\n",
748        XML_STATUS_ERROR, XML_ERROR_TAG_MISMATCH, 5, 4},
749       // Opened by tag, closed by entity; then the other way around
750       {"<!DOCTYPE t0 [\n"
751        "  <!ENTITY open '<t1>'>\n"
752        "  <!ENTITY close '</t1>'>\n"
753        "]>\n"
754        "<t0><t1>&close;&open;</t1></t0>\n",
755        XML_STATUS_OK, XML_ERROR_NONE, 5, 8},
756   };
757 
758   for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
759     const struct test_case testCase = cases[i];
760     set_subtest("cases[%d]", (int)i);
761 
762     const char *const doc = testCase.doc;
763 #if XML_GE == 1
764     const enum XML_Status expectedStatus = XML_STATUS_ERROR;
765     const enum XML_Error expectedError = XML_ERROR_ASYNC_ENTITY;
766 #else
767     const enum XML_Status expectedStatus = testCase.expectedStatusNoGE;
768     const enum XML_Error expectedError = testCase.expectedErrorNoGE;
769 #endif
770 
771     XML_Parser parser = XML_ParserCreate(NULL);
772     assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
773                                         /*isFinal=*/XML_TRUE)
774                 == expectedStatus);
775     assert_true(XML_GetErrorCode(parser) == expectedError);
776 #if XML_GE == 1
777     assert_true(XML_GetCurrentLineNumber64(parser)
778                 == testCase.expectedErrorLine);
779     assert_true(XML_GetCurrentColumnNumber64(parser)
780                 == testCase.expectedErrorColumn);
781 #endif
782     XML_ParserFree(parser);
783   }
784 }
785 END_TEST
786 
787 START_TEST(test_misc_no_infinite_loop_issue_1161) {
788   XML_Parser parser = XML_ParserCreate(NULL);
789 
790   const char *text = "<!DOCTYPE d SYSTEM 'secondary.txt'>";
791 
792   struct ExtOption options[] = {
793       {XCS("secondary.txt"),
794        "<!ENTITY % p SYSTEM 'tertiary.txt'><!ENTITY g '%p;'>"},
795       {XCS("tertiary.txt"), "<?xml version='1.0'?><a"},
796       {NULL, NULL},
797   };
798 
799   XML_SetUserData(parser, options);
800   XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
801   XML_SetExternalEntityRefHandler(parser, external_entity_optioner);
802 
803   assert_true(_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
804               == XML_STATUS_ERROR);
805 
806 #if defined(XML_DTD)
807   assert_true(XML_GetErrorCode(parser) == XML_ERROR_EXTERNAL_ENTITY_HANDLING);
808 #else
809   assert_true(XML_GetErrorCode(parser) == XML_ERROR_NO_ELEMENTS);
810 #endif
811 
812   XML_ParserFree(parser);
813 }
814 END_TEST
815 
816 START_TEST(test_misc_calls_forbidden_from_handlers) {
817   const char *const doc = "<doc>Hello world!</doc>";
818 
819   XML_Parser parser = XML_ParserCreate(NULL);
820   XML_UseParserAsHandlerArg(parser);
821   XML_SetCharacterDataHandler(parser, forbidden_calls_character_handler);
822 
823   assert_true(XML_Parse(parser, doc, (int)strlen(doc), /*isFinal=*/XML_TRUE)
824               == XML_STATUS_OK);
825 
826   XML_ParserFree(parser);
827 }
828 END_TEST
829 
830 START_TEST(test_misc_resume_parser_forbidden_from_handler) {
831   const char *const doc = "<doc>Hello world!</doc>";
832 
833   XML_Parser parser = XML_ParserCreate(NULL);
834   ResumeFromHandlerData data = {parser, 0};
835   XML_SetUserData(parser, &data);
836   XML_SetCharacterDataHandler(parser, suspend_then_resume_character_handler);
837 
838   // The handler suspends the parser, so the top-level parse reports suspension
839   // rather than completion.  The handler also asserts that resuming from inside
840   // itself is rejected.
841   assert_true(XML_Parse(parser, doc, (int)strlen(doc), /*isFinal=*/XML_TRUE)
842               == XML_STATUS_SUSPENDED);
843   assert_true(data.callCount == 1);
844 
845   XML_ParserFree(parser);
846 }
847 END_TEST
848 
849 typedef struct {
850   XML_Parser parser;
851   int converterCallCount;
852   int releaseCallCount;
853 } EncodingCallbackData;
854 
855 static int XMLCALL
856 reentrant_encoding_converter(void *userData, const char *s) {
857   EncodingCallbackData *const data = userData;
858   UNUSED_P(s);
859   data->converterCallCount++;
860   forbidden_calls_character_handler(data->parser, NULL, 0);
861   return 'A';
862 }
863 
864 static void XMLCALL
865 reentrant_encoding_release(void *userData) {
866   EncodingCallbackData *const data = userData;
867   data->releaseCallCount++;
868   forbidden_calls_character_handler(data->parser, NULL, 0);
869 }
870 
871 static int XMLCALL
872 reentrant_encoding_handler(void *userData, const XML_Char *name,
873                            XML_Encoding *info) {
874   EncodingCallbackData *const data = userData;
875   UNUSED_P(name);
876 
877   for (int i = 0; i < 256; i++)
878     info->map[i] = i;
879   info->map[0x80] = -2; // Route byte 0x80 through the custom converter.
880   info->data = data;
881   info->convert = reentrant_encoding_converter;
882   info->release = reentrant_encoding_release;
883   return XML_STATUS_OK;
884 }
885 
886 START_TEST(test_misc_unknown_encoding_callbacks_protected) {
887   const char *const doc
888       = "<?xml version='1.0' encoding='reentrant-conv'?><doc>\x80\x80</doc>";
889   XML_Parser parser = XML_ParserCreate(NULL);
890   EncodingCallbackData data = {parser, 0, 0};
891   XML_SetUnknownEncodingHandler(parser, reentrant_encoding_handler, &data);
892 
893   assert_true(XML_Parse(parser, doc, (int)strlen(doc), /*isFinal=*/XML_TRUE)
894               == XML_STATUS_OK);
895   assert_true(data.converterCallCount > 0);
896   assert_true(data.releaseCallCount == 0); // Released by XML_ParserFree below.
897 
898   XML_ParserFree(parser);
899   assert_true(data.releaseCallCount == 1);
900 }
901 END_TEST
902 
903 typedef struct {
904   XML_Parser parser;
905   int releaseCallCount;
906   uint64_t lineAtRelease;
907 } EncodingReleaseData;
908 
909 static void XMLCALL
910 position_reading_encoding_release(void *userData) {
911   EncodingReleaseData *const data = userData;
912   data->releaseCallCount++;
913   data->lineAtRelease = XML_GetCurrentLineNumber64(data->parser);
914 }
915 
916 static int XMLCALL
917 position_reading_encoding_handler(void *userData, const XML_Char *name,
918                                   XML_Encoding *info) {
919   UNUSED_P(name);
920 
921   for (int i = 0; i < 256; i++)
922     info->map[i] = i;
923   info->data = userData;
924   info->convert = NULL;
925   info->release = position_reading_encoding_release;
926   return XML_STATUS_OK;
927 }
928 
929 static void
930 run_unknown_encoding_release_getter_test(bool resetParser) {
931   const char *const doc = "<?xml version='1.0' encoding='release-order'?>\n"
932                           "<root>\n"
933                           "  <unclosed>\n"
934                           "</root>\n";
935   XML_Parser parser = XML_ParserCreate(NULL);
936   EncodingReleaseData data = {parser, 0, 0};
937 
938   assert_true(parser != NULL);
939   XML_SetUnknownEncodingHandler(parser, position_reading_encoding_handler,
940                                 &data);
941   assert_true(XML_Parse(parser, doc, (int)strlen(doc), /*isFinal=*/XML_TRUE)
942               == XML_STATUS_ERROR);
943   assert_true(XML_GetErrorCode(parser) == XML_ERROR_TAG_MISMATCH);
944 
945   if (resetParser)
946     assert_true(XML_ParserReset(parser, NULL) == XML_TRUE);
947   XML_ParserFree(parser);
948 
949   assert_true(data.releaseCallCount == 1);
950   assert_true(data.lineAtRelease == 4);
951 }
952 
953 START_TEST(test_misc_unknown_encoding_release_getter_parser_free) {
954   run_unknown_encoding_release_getter_test(false);
955 }
956 END_TEST
957 
958 START_TEST(test_misc_unknown_encoding_release_getter_parser_reset) {
959   run_unknown_encoding_release_getter_test(true);
960 }
961 END_TEST
962 
963 // General attack payload idea by Jason Kratzer of Mozilla
964 START_TEST(test_misc_low_surrogate_mozilla_bug_2053153) {
965   const char doc_before[] = "<\0!\0D\0O\0C\0T\0Y\0P\0E\0 \0d\0 \0[\0\n\0"
966                             " \0 \0<\0!\0E\0N\0T\0I\0T\0Y\0 \0e\0 \0'\0";
967   const char doc_after[] = "'\0>\0\n\0]\0>\0\n\0"
968                            "<\0r\0 \0a\0=\0'\0&\0e\0;\0'\0/\0>\0\n\0";
969 
970   for (size_t i = 1021; i <= 1025; i++) {
971     set_subtest("[%d]", (int)i);
972 
973     XML_Parser parser = XML_ParserCreate(NULL);
974 
975     assert_true(_XML_Parse_SINGLE_BYTES(parser, doc_before,
976                                         (int)sizeof(doc_before) - 1,
977                                         /*isFinal=*/XML_FALSE));
978 
979     for (size_t j = 0; j < i; j++) {
980       assert_true(
981           _XML_Parse_SINGLE_BYTES(parser, "a\\0", 2, /*isFinal=*/XML_FALSE));
982     }
983 
984     // Thinking Python, this is:
985     // ''.join([f'\\x{e:02x}' for e in '��'.encode('UTF-16-LE')])
986     assert_true(_XML_Parse_SINGLE_BYTES(parser, "\x3d\xd8\x00\xde", 4,
987                                         /*isFinal=*/XML_FALSE));
988 
989     assert_true(_XML_Parse_SINGLE_BYTES(
990         parser, doc_after, (int)sizeof(doc_after) - 1, /*isFinal=*/XML_TRUE));
991 
992     XML_ParserFree(parser);
993   }
994 }
995 END_TEST
996 
997 START_TEST(test_misc_input_2gb) {
998   XML_Parser parser;
999   const char *const doc = "<?xml version='1.0'?><doc/>";
1000   unsigned long long offset = 0;
1001   char buf[4096];
1002 
1003   if (g_chunkSize != 0) {
1004     return; // this test is slow, and doesn't use _XML_Parse_SINGLE_BYTES().
1005   }
1006 
1007   memset(buf, ' ', sizeof(buf));
1008 
1009   parser = XML_ParserCreate(NULL);
1010 
1011   assert_true(XML_Parse(parser, doc, (int)strlen(doc), XML_FALSE)
1012               == XML_STATUS_OK);
1013   offset += strlen(doc);
1014 
1015   while (offset < 2ULL * 1024 * 1024 * 1024) {
1016     assert_true(XML_Parse(parser, buf, sizeof(buf), XML_FALSE)
1017                 == XML_STATUS_OK);
1018     offset += sizeof(buf);
1019   }
1020 
1021   assert_true(XML_Parse(parser, NULL, 0, XML_TRUE) == XML_STATUS_OK);
1022 
1023   XML_ParserFree(parser);
1024 }
1025 END_TEST
1026 
1027 void
1028 make_miscellaneous_test_case(Suite *s) {
1029   TCase *tc_misc = tcase_create("miscellaneous tests");
1030 
1031   suite_add_tcase(s, tc_misc);
1032   tcase_add_checked_fixture(tc_misc, NULL, basic_teardown);
1033 
1034   tcase_add_test(tc_misc, test_misc_alloc_create_parser);
1035   tcase_add_test(tc_misc, test_misc_alloc_create_parser_with_encoding);
1036   tcase_add_test(tc_misc, test_misc_null_parser);
1037   tcase_add_test(tc_misc, test_misc_error_string);
1038   tcase_add_test(tc_misc, test_misc_version);
1039   tcase_add_test(tc_misc, test_misc_features);
1040   tcase_add_test(tc_misc, test_misc_attribute_leak);
1041   tcase_add_test(tc_misc, test_misc_utf16le);
1042   tcase_add_test(tc_misc, test_misc_stop_during_end_handler_issue_240_1);
1043   tcase_add_test(tc_misc, test_misc_stop_during_end_handler_issue_240_2);
1044   tcase_add_test__ifdef_xml_dtd(
1045       tc_misc, test_misc_deny_internal_entity_closing_doctype_issue_317);
1046   tcase_add_test(tc_misc, test_misc_tag_mismatch_reset_leak);
1047   tcase_add_test(tc_misc,
1048                  test_misc_create_external_entity_parser_with_null_context);
1049   tcase_add_test(tc_misc, test_misc_general_entities_support);
1050   tcase_add_test(tc_misc, test_misc_char_handler_stop_without_leak);
1051   tcase_add_test(tc_misc, test_misc_resumeparser_not_crashing);
1052   tcase_add_test(tc_misc, test_misc_stopparser_rejects_unstarted_parser);
1053   tcase_add_test__if_xml_ge(tc_misc, test_renter_loop_finite_content);
1054   tcase_add_test(tc_misc, test_misc_expected_event_ptr_issue_980);
1055   tcase_add_test(tc_misc, test_misc_sync_entity_tolerated);
1056   tcase_add_test(tc_misc, test_misc_async_entity_rejected);
1057   tcase_add_test(tc_misc, test_misc_no_infinite_loop_issue_1161);
1058   tcase_add_test(tc_misc, test_misc_calls_forbidden_from_handlers);
1059   tcase_add_test(tc_misc, test_misc_resume_parser_forbidden_from_handler);
1060   tcase_add_test(tc_misc, test_misc_unknown_encoding_callbacks_protected);
1061   tcase_add_test(tc_misc,
1062                  test_misc_unknown_encoding_release_getter_parser_free);
1063   tcase_add_test(tc_misc,
1064                  test_misc_unknown_encoding_release_getter_parser_reset);
1065   tcase_add_test(tc_misc, test_misc_input_2gb);
1066   tcase_add_test(tc_misc, test_misc_low_surrogate_mozilla_bug_2053153);
1067 }
1068