xref: /freebsd/contrib/expat/tests/misc_tests.c (revision 627b778d9e6b603a44a010d22d823ca7c392b363)
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-2025 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    Licensed under the MIT license:
23 
24    Permission is  hereby granted,  free of charge,  to any  person obtaining
25    a  copy  of  this  software   and  associated  documentation  files  (the
26    "Software"),  to  deal in  the  Software  without restriction,  including
27    without  limitation the  rights  to use,  copy,  modify, merge,  publish,
28    distribute, sublicense, and/or sell copies of the Software, and to permit
29    persons  to whom  the Software  is  furnished to  do so,  subject to  the
30    following conditions:
31 
32    The above copyright  notice and this permission notice  shall be included
33    in all copies or substantial portions of the Software.
34 
35    THE  SOFTWARE  IS  PROVIDED  "AS  IS",  WITHOUT  WARRANTY  OF  ANY  KIND,
36    EXPRESS  OR IMPLIED,  INCLUDING  BUT  NOT LIMITED  TO  THE WARRANTIES  OF
37    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
38    NO EVENT SHALL THE AUTHORS OR  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
39    DAMAGES OR  OTHER LIABILITY, WHETHER  IN AN  ACTION OF CONTRACT,  TORT OR
40    OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
41    USE OR OTHER DEALINGS IN THE SOFTWARE.
42 */
43 
44 #if defined(NDEBUG)
45 #  undef NDEBUG /* because test suite relies on assert(...) at the moment */
46 #endif
47 
48 #include <assert.h>
49 #include <string.h>
50 
51 #include "expat_config.h"
52 
53 #include "expat.h"
54 #include "internal.h"
55 #include "minicheck.h"
56 #include "memcheck.h"
57 #include "common.h"
58 #include "ascii.h" /* for ASCII_xxx */
59 #include "handlers.h"
60 #include "misc_tests.h"
61 
62 void XMLCALL accumulate_characters_ext_handler(void *userData,
63                                                const XML_Char *s, int len);
64 
65 /* Test that a failure to allocate the parser structure fails gracefully */
START_TEST(test_misc_alloc_create_parser)66 START_TEST(test_misc_alloc_create_parser) {
67   XML_Memory_Handling_Suite memsuite = {duff_allocator, realloc, free};
68   unsigned int i;
69   const unsigned int max_alloc_count = 10;
70 
71   /* Something this simple shouldn't need more than 10 allocations */
72   for (i = 0; i < max_alloc_count; i++) {
73     g_allocation_count = (int)i;
74     g_parser = XML_ParserCreate_MM(NULL, &memsuite, NULL);
75     if (g_parser != NULL)
76       break;
77   }
78   if (i == 0)
79     fail("Parser unexpectedly ignored failing allocator");
80   else if (i == max_alloc_count)
81     fail("Parser not created with max allocation count");
82 }
83 END_TEST
84 
85 /* Test memory allocation failures for a parser with an encoding */
START_TEST(test_misc_alloc_create_parser_with_encoding)86 START_TEST(test_misc_alloc_create_parser_with_encoding) {
87   XML_Memory_Handling_Suite memsuite = {duff_allocator, realloc, free};
88   unsigned int i;
89   const unsigned int max_alloc_count = 10;
90 
91   /* Try several levels of allocation */
92   for (i = 0; i < max_alloc_count; i++) {
93     g_allocation_count = (int)i;
94     g_parser = XML_ParserCreate_MM(XCS("us-ascii"), &memsuite, NULL);
95     if (g_parser != NULL)
96       break;
97   }
98   if (i == 0)
99     fail("Parser ignored failing allocator");
100   else if (i == max_alloc_count)
101     fail("Parser not created with max allocation count");
102 }
103 END_TEST
104 
105 /* Test that freeing a NULL parser doesn't cause an explosion.
106  * (Not actually tested anywhere else)
107  */
START_TEST(test_misc_null_parser)108 START_TEST(test_misc_null_parser) {
109   XML_ParserFree(NULL);
110 }
111 END_TEST
112 
113 #if defined(__has_feature)
114 #  if __has_feature(undefined_behavior_sanitizer)
115 #    define EXPAT_TESTS_UBSAN 1
116 #  else
117 #    define EXPAT_TESTS_UBSAN 0
118 #  endif
119 #else
120 #  define EXPAT_TESTS_UBSAN 0
121 #endif
122 
123 /* Test that XML_ErrorString rejects out-of-range codes */
START_TEST(test_misc_error_string)124 START_TEST(test_misc_error_string) {
125 #if ! EXPAT_TESTS_UBSAN // because this would trigger UBSan
126   union {
127     enum XML_Error xml_error;
128     int integer;
129   } trickery;
130 
131   assert_true(sizeof(enum XML_Error) == sizeof(int)); // self-test
132 
133   trickery.integer = -1;
134   if (XML_ErrorString(trickery.xml_error) != NULL)
135     fail("Negative error code not rejected");
136 
137   trickery.integer = 100;
138   if (XML_ErrorString(trickery.xml_error) != NULL)
139     fail("Large error code not rejected");
140 #endif
141 }
142 END_TEST
143 
144 /* Test the version information is consistent */
145 
146 /* Since we are working in XML_LChars (potentially 16-bits), we
147  * can't use the standard C library functions for character
148  * manipulation and have to roll our own.
149  */
150 static int
parse_version(const XML_LChar * version_text,XML_Expat_Version * version_struct)151 parse_version(const XML_LChar *version_text,
152               XML_Expat_Version *version_struct) {
153   if (! version_text)
154     return XML_FALSE;
155 
156   while (*version_text != 0x00) {
157     if (*version_text >= ASCII_0 && *version_text <= ASCII_9)
158       break;
159     version_text++;
160   }
161   if (*version_text == 0x00)
162     return XML_FALSE;
163 
164   /* version_struct->major = strtoul(version_text, 10, &version_text) */
165   version_struct->major = 0;
166   while (*version_text >= ASCII_0 && *version_text <= ASCII_9) {
167     version_struct->major
168         = 10 * version_struct->major + (*version_text++ - ASCII_0);
169   }
170   if (*version_text++ != ASCII_PERIOD)
171     return XML_FALSE;
172 
173   /* Now for the minor version number */
174   version_struct->minor = 0;
175   while (*version_text >= ASCII_0 && *version_text <= ASCII_9) {
176     version_struct->minor
177         = 10 * version_struct->minor + (*version_text++ - ASCII_0);
178   }
179   if (*version_text++ != ASCII_PERIOD)
180     return XML_FALSE;
181 
182   /* Finally the micro version number */
183   version_struct->micro = 0;
184   while (*version_text >= ASCII_0 && *version_text <= ASCII_9) {
185     version_struct->micro
186         = 10 * version_struct->micro + (*version_text++ - ASCII_0);
187   }
188   if (*version_text != 0x00)
189     return XML_FALSE;
190   return XML_TRUE;
191 }
192 
193 static int
versions_equal(const XML_Expat_Version * first,const XML_Expat_Version * second)194 versions_equal(const XML_Expat_Version *first,
195                const XML_Expat_Version *second) {
196   return (first->major == second->major && first->minor == second->minor
197           && first->micro == second->micro);
198 }
199 
START_TEST(test_misc_version)200 START_TEST(test_misc_version) {
201   XML_Expat_Version read_version = XML_ExpatVersionInfo();
202   /* Silence compiler warning with the following assignment */
203   XML_Expat_Version parsed_version = {0, 0, 0};
204   const XML_LChar *version_text = XML_ExpatVersion();
205 
206   if (version_text == NULL)
207     fail("Could not obtain version text");
208   assert(version_text != NULL);
209   if (! parse_version(version_text, &parsed_version))
210     fail("Unable to parse version text");
211   if (! versions_equal(&read_version, &parsed_version))
212     fail("Version mismatch");
213 
214   if (xcstrcmp(version_text, XCS("expat_2.7.2"))
215       != 0) /* needs bump on releases */
216     fail("XML_*_VERSION in expat.h out of sync?\n");
217 }
218 END_TEST
219 
220 /* Test feature information */
START_TEST(test_misc_features)221 START_TEST(test_misc_features) {
222   const XML_Feature *features = XML_GetFeatureList();
223 
224   /* Prevent problems with double-freeing parsers */
225   g_parser = NULL;
226   if (features == NULL) {
227     fail("Failed to get feature information");
228   } else {
229     /* Loop through the features checking what we can */
230     while (features->feature != XML_FEATURE_END) {
231       switch (features->feature) {
232       case XML_FEATURE_SIZEOF_XML_CHAR:
233         if (features->value != sizeof(XML_Char))
234           fail("Incorrect size of XML_Char");
235         break;
236       case XML_FEATURE_SIZEOF_XML_LCHAR:
237         if (features->value != sizeof(XML_LChar))
238           fail("Incorrect size of XML_LChar");
239         break;
240       default:
241         break;
242       }
243       features++;
244     }
245   }
246 }
247 END_TEST
248 
249 /* Regression test for GitHub Issue #17: memory leak parsing attribute
250  * values with mixed bound and unbound namespaces.
251  */
START_TEST(test_misc_attribute_leak)252 START_TEST(test_misc_attribute_leak) {
253   const char *text = "<D xmlns:L=\"D\" l:a='' L:a=''/>";
254   XML_Memory_Handling_Suite memsuite
255       = {tracking_malloc, tracking_realloc, tracking_free};
256 
257   g_parser = XML_ParserCreate_MM(XCS("UTF-8"), &memsuite, XCS("\n"));
258   expect_failure(text, XML_ERROR_UNBOUND_PREFIX, "Unbound prefixes not found");
259   XML_ParserFree(g_parser);
260   /* Prevent the teardown trying to double free */
261   g_parser = NULL;
262 
263   if (! tracking_report())
264     fail("Memory leak found");
265 }
266 END_TEST
267 
268 /* Test parser created for UTF-16LE is successful */
START_TEST(test_misc_utf16le)269 START_TEST(test_misc_utf16le) {
270   const char text[] =
271       /* <?xml version='1.0'?><q>Hi</q> */
272       "<\0?\0x\0m\0l\0 \0"
273       "v\0e\0r\0s\0i\0o\0n\0=\0'\0\x31\0.\0\x30\0'\0?\0>\0"
274       "<\0q\0>\0H\0i\0<\0/\0q\0>\0";
275   const XML_Char *expected = XCS("Hi");
276   CharData storage;
277 
278   g_parser = XML_ParserCreate(XCS("UTF-16LE"));
279   if (g_parser == NULL)
280     fail("Parser not created");
281 
282   CharData_Init(&storage);
283   XML_SetUserData(g_parser, &storage);
284   XML_SetCharacterDataHandler(g_parser, accumulate_characters);
285   if (_XML_Parse_SINGLE_BYTES(g_parser, text, (int)sizeof(text) - 1, XML_TRUE)
286       == XML_STATUS_ERROR)
287     xml_failure(g_parser);
288   CharData_CheckXMLChars(&storage, expected);
289 }
290 END_TEST
291 
START_TEST(test_misc_stop_during_end_handler_issue_240_1)292 START_TEST(test_misc_stop_during_end_handler_issue_240_1) {
293   XML_Parser parser;
294   DataIssue240 *mydata;
295   enum XML_Status result;
296   const char *const doc1 = "<doc><e1/><e><foo/></e></doc>";
297 
298   parser = XML_ParserCreate(NULL);
299   XML_SetElementHandler(parser, start_element_issue_240, end_element_issue_240);
300   mydata = (DataIssue240 *)malloc(sizeof(DataIssue240));
301   assert_true(mydata != NULL);
302   mydata->parser = parser;
303   mydata->deep = 0;
304   XML_SetUserData(parser, mydata);
305 
306   result = _XML_Parse_SINGLE_BYTES(parser, doc1, (int)strlen(doc1), 1);
307   XML_ParserFree(parser);
308   free(mydata);
309   if (result != XML_STATUS_ERROR)
310     fail("Stopping the parser did not work as expected");
311 }
312 END_TEST
313 
START_TEST(test_misc_stop_during_end_handler_issue_240_2)314 START_TEST(test_misc_stop_during_end_handler_issue_240_2) {
315   XML_Parser parser;
316   DataIssue240 *mydata;
317   enum XML_Status result;
318   const char *const doc2 = "<doc><elem/></doc>";
319 
320   parser = XML_ParserCreate(NULL);
321   XML_SetElementHandler(parser, start_element_issue_240, end_element_issue_240);
322   mydata = (DataIssue240 *)malloc(sizeof(DataIssue240));
323   assert_true(mydata != NULL);
324   mydata->parser = parser;
325   mydata->deep = 0;
326   XML_SetUserData(parser, mydata);
327 
328   result = _XML_Parse_SINGLE_BYTES(parser, doc2, (int)strlen(doc2), 1);
329   XML_ParserFree(parser);
330   free(mydata);
331   if (result != XML_STATUS_ERROR)
332     fail("Stopping the parser did not work as expected");
333 }
334 END_TEST
335 
START_TEST(test_misc_deny_internal_entity_closing_doctype_issue_317)336 START_TEST(test_misc_deny_internal_entity_closing_doctype_issue_317) {
337   const char *const inputOne
338       = "<!DOCTYPE d [\n"
339         "<!ENTITY % element_d '<!ELEMENT d (#PCDATA)*>'>\n"
340         "%element_d;\n"
341         "<!ENTITY % e ']><d/>'>\n"
342         "\n"
343         "%e;";
344   const char *const inputTwo
345       = "<!DOCTYPE d [\n"
346         "<!ENTITY % element_d '<!ELEMENT d (#PCDATA)*>'>\n"
347         "%element_d;\n"
348         "<!ENTITY % e1 ']><d/>'><!ENTITY % e2 '&#37;e1;'>\n"
349         "\n"
350         "%e2;";
351   const char *const inputThree
352       = "<!DOCTYPE d [\n"
353         "<!ENTITY % element_d '<!ELEMENT d (#PCDATA)*>'>\n"
354         "%element_d;\n"
355         "<!ENTITY % e ']><d'>\n"
356         "\n"
357         "%e;/>";
358   const char *const inputIssue317
359       = "<!DOCTYPE doc [\n"
360         "<!ENTITY % element_doc '<!ELEMENT doc (#PCDATA)*>'>\n"
361         "%element_doc;\n"
362         "<!ENTITY % foo ']>\n"
363         "<doc>Hell<oc (#PCDATA)*>'>\n"
364         "%foo;\n"
365         "]>\n"
366         "<doc>Hello, world</dVc>";
367 
368   const char *const inputs[] = {inputOne, inputTwo, inputThree, inputIssue317};
369   const XML_Bool suspendOrNot[] = {XML_FALSE, XML_TRUE};
370   size_t inputIndex = 0;
371 
372   for (; inputIndex < sizeof(inputs) / sizeof(inputs[0]); inputIndex++) {
373     for (size_t suspendOrNotIndex = 0;
374          suspendOrNotIndex < sizeof(suspendOrNot) / sizeof(suspendOrNot[0]);
375          suspendOrNotIndex++) {
376       const char *const input = inputs[inputIndex];
377       const XML_Bool suspend = suspendOrNot[suspendOrNotIndex];
378       if (suspend && (g_chunkSize > 0)) {
379         // We cannot use _XML_Parse_SINGLE_BYTES below due to suspension, and
380         // so chunk sizes >0 would only repeat the very same test
381         // due to use of plain XML_Parse; we are saving upon that runtime:
382         return;
383       }
384 
385       set_subtest("[input=%d suspend=%s] %s", (int)inputIndex,
386                   suspend ? "true" : "false", input);
387       XML_Parser parser;
388       enum XML_Status parseResult;
389       int setParamEntityResult;
390       XML_Size lineNumber;
391       XML_Size columnNumber;
392 
393       parser = XML_ParserCreate(NULL);
394       setParamEntityResult
395           = XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
396       if (setParamEntityResult != 1)
397         fail("Failed to set XML_PARAM_ENTITY_PARSING_ALWAYS.");
398 
399       if (suspend) {
400         XML_SetUserData(parser, parser);
401         XML_SetElementDeclHandler(parser, suspend_after_element_declaration);
402       }
403 
404       if (suspend) {
405         // can't use SINGLE_BYTES here, because it'll return early on
406         // suspension, and we won't know exactly how much input we actually
407         // managed to give Expat.
408         parseResult = XML_Parse(parser, input, (int)strlen(input), 0);
409 
410         while (parseResult == XML_STATUS_SUSPENDED) {
411           parseResult = XML_ResumeParser(parser);
412         }
413 
414         if (parseResult != XML_STATUS_ERROR) {
415           // can't use SINGLE_BYTES here, because it'll return early on
416           // suspension, and we won't know exactly how much input we actually
417           // managed to give Expat.
418           parseResult = XML_Parse(parser, "", 0, 1);
419         }
420 
421         while (parseResult == XML_STATUS_SUSPENDED) {
422           parseResult = XML_ResumeParser(parser);
423         }
424       } else {
425         parseResult
426             = _XML_Parse_SINGLE_BYTES(parser, input, (int)strlen(input), 0);
427 
428         if (parseResult != XML_STATUS_ERROR) {
429           parseResult = _XML_Parse_SINGLE_BYTES(parser, "", 0, 1);
430         }
431       }
432 
433       if (parseResult != XML_STATUS_ERROR) {
434         fail("Parsing was expected to fail but succeeded.");
435       }
436 
437       if (XML_GetErrorCode(parser) != XML_ERROR_INVALID_TOKEN)
438         fail("Error code does not match XML_ERROR_INVALID_TOKEN");
439 
440       lineNumber = XML_GetCurrentLineNumber(parser);
441       if (lineNumber != 6)
442         fail("XML_GetCurrentLineNumber does not work as expected.");
443 
444       columnNumber = XML_GetCurrentColumnNumber(parser);
445       if (columnNumber != 0)
446         fail("XML_GetCurrentColumnNumber does not work as expected.");
447 
448       XML_ParserFree(parser);
449     }
450   }
451 }
452 END_TEST
453 
START_TEST(test_misc_tag_mismatch_reset_leak)454 START_TEST(test_misc_tag_mismatch_reset_leak) {
455 #ifdef XML_NS
456   const char *const text = "<open xmlns='https://namespace1.test'></close>";
457   XML_Parser parser = XML_ParserCreateNS(NULL, XCS('\n'));
458 
459   if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
460       != XML_STATUS_ERROR)
461     fail("Call to parse was expected to fail");
462   if (XML_GetErrorCode(parser) != XML_ERROR_TAG_MISMATCH)
463     fail("Call to parse was expected to fail from a closing tag mismatch");
464 
465   XML_ParserReset(parser, NULL);
466 
467   if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
468       != XML_STATUS_ERROR)
469     fail("Call to parse was expected to fail");
470   if (XML_GetErrorCode(parser) != XML_ERROR_TAG_MISMATCH)
471     fail("Call to parse was expected to fail from a closing tag mismatch");
472 
473   XML_ParserFree(parser);
474 #endif
475 }
476 END_TEST
477 
START_TEST(test_misc_create_external_entity_parser_with_null_context)478 START_TEST(test_misc_create_external_entity_parser_with_null_context) {
479   // With XML_DTD undefined, the only supported case of external entities
480   // is pattern "<!ENTITY entity123 SYSTEM 'filename123'>". A NULL context
481   // was causing a segfault through a null pointer dereference in function
482   // setContext, previously.
483   XML_Parser parser = XML_ParserCreate(NULL);
484   XML_Parser ext_parser = XML_ExternalEntityParserCreate(parser, NULL, NULL);
485 #ifdef XML_DTD
486   assert_true(ext_parser != NULL);
487   XML_ParserFree(ext_parser);
488 #else
489   assert_true(ext_parser == NULL);
490 #endif /* XML_DTD */
491   XML_ParserFree(parser);
492 }
493 END_TEST
494 
START_TEST(test_misc_general_entities_support)495 START_TEST(test_misc_general_entities_support) {
496   const char *const doc
497       = "<!DOCTYPE r [\n"
498         "<!ENTITY e1 'v1'>\n"
499         "<!ENTITY e2 SYSTEM 'v2'>\n"
500         "]>\n"
501         "<r a1='[&e1;]'>[&e1;][&e2;][&amp;&apos;&gt;&lt;&quot;]</r>";
502 
503   CharData storage;
504   CharData_Init(&storage);
505 
506   XML_Parser parser = XML_ParserCreate(NULL);
507   XML_SetUserData(parser, &storage);
508   XML_SetStartElementHandler(parser, accumulate_start_element);
509   XML_SetExternalEntityRefHandler(parser,
510                                   external_entity_failer__if_not_xml_ge);
511   XML_SetEntityDeclHandler(parser, accumulate_entity_decl);
512   XML_SetCharacterDataHandler(parser, accumulate_characters);
513 
514   if (_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc), XML_TRUE)
515       != XML_STATUS_OK) {
516     xml_failure(parser);
517   }
518 
519   XML_ParserFree(parser);
520 
521   CharData_CheckXMLChars(&storage,
522   /* clang-format off */
523 #if XML_GE == 1
524                          XCS("e1=v1\n")
525                          XCS("e2=(null)\n")
526                          XCS("(r(a1=[v1]))\n")
527                          XCS("[v1][][&'><\"]")
528 #else
529                          XCS("e1=&amp;e1;\n")
530                          XCS("e2=(null)\n")
531                          XCS("(r(a1=[&e1;]))\n")
532                          XCS("[&e1;][&e2;][&'><\"]")
533 #endif
534   );
535   /* clang-format on */
536 }
537 END_TEST
538 
539 static void XMLCALL
resumable_stopping_character_handler(void * userData,const XML_Char * s,int len)540 resumable_stopping_character_handler(void *userData, const XML_Char *s,
541                                      int len) {
542   UNUSED_P(s);
543   UNUSED_P(len);
544   XML_Parser parser = (XML_Parser)userData;
545   XML_StopParser(parser, XML_TRUE);
546 }
547 
548 // NOTE: This test needs active LeakSanitizer to be of actual use
START_TEST(test_misc_char_handler_stop_without_leak)549 START_TEST(test_misc_char_handler_stop_without_leak) {
550   const char *const data
551       = "<!DOCTYPE t1[<!ENTITY e1 'angle<'><!ENTITY e2 '&e1;'>]><t1>&e2;";
552   XML_Parser parser = XML_ParserCreate(NULL);
553   assert_true(parser != NULL);
554   XML_SetUserData(parser, parser);
555   XML_SetCharacterDataHandler(parser, resumable_stopping_character_handler);
556   _XML_Parse_SINGLE_BYTES(parser, data, (int)strlen(data), XML_FALSE);
557   XML_ParserFree(parser);
558 }
559 END_TEST
560 
START_TEST(test_misc_resumeparser_not_crashing)561 START_TEST(test_misc_resumeparser_not_crashing) {
562   XML_Parser parser = XML_ParserCreate(NULL);
563   XML_GetBuffer(parser, 1);
564   XML_StopParser(parser, /*resumable=*/XML_TRUE);
565   XML_ResumeParser(parser); // could crash here, previously
566   XML_ParserFree(parser);
567 }
568 END_TEST
569 
START_TEST(test_misc_stopparser_rejects_unstarted_parser)570 START_TEST(test_misc_stopparser_rejects_unstarted_parser) {
571   const XML_Bool cases[] = {XML_TRUE, XML_FALSE};
572   for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
573     const XML_Bool resumable = cases[i];
574     XML_Parser parser = XML_ParserCreate(NULL);
575     assert_true(XML_GetErrorCode(parser) == XML_ERROR_NONE);
576     assert_true(XML_StopParser(parser, resumable) == XML_STATUS_ERROR);
577     assert_true(XML_GetErrorCode(parser) == XML_ERROR_NOT_STARTED);
578     XML_ParserFree(parser);
579   }
580 }
581 END_TEST
582 
583 /* Adaptation of accumulate_characters that takes ExtHdlrData input to work with
584  * test_renter_loop_finite_content below */
585 void XMLCALL
accumulate_characters_ext_handler(void * userData,const XML_Char * s,int len)586 accumulate_characters_ext_handler(void *userData, const XML_Char *s, int len) {
587   ExtHdlrData *const test_data = (ExtHdlrData *)userData;
588   CharData_AppendXMLChars(test_data->storage, s, len);
589 }
590 
591 /* Test that internalEntityProcessor does not re-enter forever;
592  * based on files tests/xmlconf/xmltest/valid/ext-sa/012.{xml,ent} */
START_TEST(test_renter_loop_finite_content)593 START_TEST(test_renter_loop_finite_content) {
594   CharData storage;
595   CharData_Init(&storage);
596   const char *const text = "<!DOCTYPE doc [\n"
597                            "<!ENTITY e1 '&e2;'>\n"
598                            "<!ENTITY e2 '&e3;'>\n"
599                            "<!ENTITY e3 SYSTEM '012.ent'>\n"
600                            "<!ENTITY e4 '&e5;'>\n"
601                            "<!ENTITY e5 '(e5)'>\n"
602                            "<!ELEMENT doc (#PCDATA)>\n"
603                            "]>\n"
604                            "<doc>&e1;</doc>\n";
605   ExtHdlrData test_data = {"&e4;\n", external_entity_null_loader, &storage};
606   const XML_Char *const expected = XCS("(e5)\n");
607 
608   XML_Parser parser = XML_ParserCreate(NULL);
609   assert_true(parser != NULL);
610   XML_SetUserData(parser, &test_data);
611   XML_SetExternalEntityRefHandler(parser, external_entity_oneshot_loader);
612   XML_SetCharacterDataHandler(parser, accumulate_characters_ext_handler);
613   if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
614       == XML_STATUS_ERROR)
615     xml_failure(parser);
616 
617   CharData_CheckXMLChars(&storage, expected);
618   XML_ParserFree(parser);
619 }
620 END_TEST
621 
622 // Inspired by function XML_OriginalString of Perl's XML::Parser
623 static char *
dup_original_string(XML_Parser parser)624 dup_original_string(XML_Parser parser) {
625   const int byte_count = XML_GetCurrentByteCount(parser);
626 
627   assert_true(byte_count >= 0);
628 
629   int offset = -1;
630   int size = -1;
631 
632   const char *const context = XML_GetInputContext(parser, &offset, &size);
633 
634 #if XML_CONTEXT_BYTES > 0
635   assert_true(context != NULL);
636   assert_true(offset >= 0);
637   assert_true(size >= 0);
638   return portable_strndup(context + offset, byte_count);
639 #else
640   assert_true(context == NULL);
641   return NULL;
642 #endif
643 }
644 
645 static void
on_characters_issue_980(void * userData,const XML_Char * s,int len)646 on_characters_issue_980(void *userData, const XML_Char *s, int len) {
647   (void)s;
648   (void)len;
649   XML_Parser parser = (XML_Parser)userData;
650 
651   char *const original_string = dup_original_string(parser);
652 
653 #if XML_CONTEXT_BYTES > 0
654   assert_true(original_string != NULL);
655   assert_true(strcmp(original_string, "&draft.day;") == 0);
656   free(original_string);
657 #else
658   assert_true(original_string == NULL);
659 #endif
660 }
661 
START_TEST(test_misc_expected_event_ptr_issue_980)662 START_TEST(test_misc_expected_event_ptr_issue_980) {
663   // NOTE: This is a tiny subset of sample "REC-xml-19980210.xml"
664   //       from Perl's XML::Parser
665   const char *const doc = "<!DOCTYPE day [\n"
666                           "  <!ENTITY draft.day '10'>\n"
667                           "]>\n"
668                           "<day>&draft.day;</day>\n";
669 
670   XML_Parser parser = XML_ParserCreate(NULL);
671   XML_SetUserData(parser, parser);
672   XML_SetCharacterDataHandler(parser, on_characters_issue_980);
673 
674   assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
675                                       /*isFinal=*/XML_TRUE)
676               == XML_STATUS_OK);
677 
678   XML_ParserFree(parser);
679 }
680 END_TEST
681 
682 void
make_miscellaneous_test_case(Suite * s)683 make_miscellaneous_test_case(Suite *s) {
684   TCase *tc_misc = tcase_create("miscellaneous tests");
685 
686   suite_add_tcase(s, tc_misc);
687   tcase_add_checked_fixture(tc_misc, NULL, basic_teardown);
688 
689   tcase_add_test(tc_misc, test_misc_alloc_create_parser);
690   tcase_add_test(tc_misc, test_misc_alloc_create_parser_with_encoding);
691   tcase_add_test(tc_misc, test_misc_null_parser);
692   tcase_add_test(tc_misc, test_misc_error_string);
693   tcase_add_test(tc_misc, test_misc_version);
694   tcase_add_test(tc_misc, test_misc_features);
695   tcase_add_test(tc_misc, test_misc_attribute_leak);
696   tcase_add_test(tc_misc, test_misc_utf16le);
697   tcase_add_test(tc_misc, test_misc_stop_during_end_handler_issue_240_1);
698   tcase_add_test(tc_misc, test_misc_stop_during_end_handler_issue_240_2);
699   tcase_add_test__ifdef_xml_dtd(
700       tc_misc, test_misc_deny_internal_entity_closing_doctype_issue_317);
701   tcase_add_test(tc_misc, test_misc_tag_mismatch_reset_leak);
702   tcase_add_test(tc_misc,
703                  test_misc_create_external_entity_parser_with_null_context);
704   tcase_add_test(tc_misc, test_misc_general_entities_support);
705   tcase_add_test(tc_misc, test_misc_char_handler_stop_without_leak);
706   tcase_add_test(tc_misc, test_misc_resumeparser_not_crashing);
707   tcase_add_test(tc_misc, test_misc_stopparser_rejects_unstarted_parser);
708   tcase_add_test__if_xml_ge(tc_misc, test_renter_loop_finite_content);
709   tcase_add_test(tc_misc, test_misc_expected_event_ptr_issue_980);
710 }
711