xref: /freebsd/contrib/expat/tests/misc_tests.c (revision fe9278888fd4414abe2d922e469cf608005f4c65)
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 = 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 = 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.1"))) /* needs bump on releases */
215     fail("XML_*_VERSION in expat.h out of sync?\n");
216 }
217 END_TEST
218 
219 /* Test feature information */
START_TEST(test_misc_features)220 START_TEST(test_misc_features) {
221   const XML_Feature *features = XML_GetFeatureList();
222 
223   /* Prevent problems with double-freeing parsers */
224   g_parser = NULL;
225   if (features == NULL) {
226     fail("Failed to get feature information");
227   } else {
228     /* Loop through the features checking what we can */
229     while (features->feature != XML_FEATURE_END) {
230       switch (features->feature) {
231       case XML_FEATURE_SIZEOF_XML_CHAR:
232         if (features->value != sizeof(XML_Char))
233           fail("Incorrect size of XML_Char");
234         break;
235       case XML_FEATURE_SIZEOF_XML_LCHAR:
236         if (features->value != sizeof(XML_LChar))
237           fail("Incorrect size of XML_LChar");
238         break;
239       default:
240         break;
241       }
242       features++;
243     }
244   }
245 }
246 END_TEST
247 
248 /* Regression test for GitHub Issue #17: memory leak parsing attribute
249  * values with mixed bound and unbound namespaces.
250  */
START_TEST(test_misc_attribute_leak)251 START_TEST(test_misc_attribute_leak) {
252   const char *text = "<D xmlns:L=\"D\" l:a='' L:a=''/>";
253   XML_Memory_Handling_Suite memsuite
254       = {tracking_malloc, tracking_realloc, tracking_free};
255 
256   g_parser = XML_ParserCreate_MM(XCS("UTF-8"), &memsuite, XCS("\n"));
257   expect_failure(text, XML_ERROR_UNBOUND_PREFIX, "Unbound prefixes not found");
258   XML_ParserFree(g_parser);
259   /* Prevent the teardown trying to double free */
260   g_parser = NULL;
261 
262   if (! tracking_report())
263     fail("Memory leak found");
264 }
265 END_TEST
266 
267 /* Test parser created for UTF-16LE is successful */
START_TEST(test_misc_utf16le)268 START_TEST(test_misc_utf16le) {
269   const char text[] =
270       /* <?xml version='1.0'?><q>Hi</q> */
271       "<\0?\0x\0m\0l\0 \0"
272       "v\0e\0r\0s\0i\0o\0n\0=\0'\0\x31\0.\0\x30\0'\0?\0>\0"
273       "<\0q\0>\0H\0i\0<\0/\0q\0>\0";
274   const XML_Char *expected = XCS("Hi");
275   CharData storage;
276 
277   g_parser = XML_ParserCreate(XCS("UTF-16LE"));
278   if (g_parser == NULL)
279     fail("Parser not created");
280 
281   CharData_Init(&storage);
282   XML_SetUserData(g_parser, &storage);
283   XML_SetCharacterDataHandler(g_parser, accumulate_characters);
284   if (_XML_Parse_SINGLE_BYTES(g_parser, text, (int)sizeof(text) - 1, XML_TRUE)
285       == XML_STATUS_ERROR)
286     xml_failure(g_parser);
287   CharData_CheckXMLChars(&storage, expected);
288 }
289 END_TEST
290 
START_TEST(test_misc_stop_during_end_handler_issue_240_1)291 START_TEST(test_misc_stop_during_end_handler_issue_240_1) {
292   XML_Parser parser;
293   DataIssue240 *mydata;
294   enum XML_Status result;
295   const char *const doc1 = "<doc><e1/><e><foo/></e></doc>";
296 
297   parser = XML_ParserCreate(NULL);
298   XML_SetElementHandler(parser, start_element_issue_240, end_element_issue_240);
299   mydata = (DataIssue240 *)malloc(sizeof(DataIssue240));
300   assert_true(mydata != NULL);
301   mydata->parser = parser;
302   mydata->deep = 0;
303   XML_SetUserData(parser, mydata);
304 
305   result = _XML_Parse_SINGLE_BYTES(parser, doc1, (int)strlen(doc1), 1);
306   XML_ParserFree(parser);
307   free(mydata);
308   if (result != XML_STATUS_ERROR)
309     fail("Stopping the parser did not work as expected");
310 }
311 END_TEST
312 
START_TEST(test_misc_stop_during_end_handler_issue_240_2)313 START_TEST(test_misc_stop_during_end_handler_issue_240_2) {
314   XML_Parser parser;
315   DataIssue240 *mydata;
316   enum XML_Status result;
317   const char *const doc2 = "<doc><elem/></doc>";
318 
319   parser = XML_ParserCreate(NULL);
320   XML_SetElementHandler(parser, start_element_issue_240, end_element_issue_240);
321   mydata = (DataIssue240 *)malloc(sizeof(DataIssue240));
322   assert_true(mydata != NULL);
323   mydata->parser = parser;
324   mydata->deep = 0;
325   XML_SetUserData(parser, mydata);
326 
327   result = _XML_Parse_SINGLE_BYTES(parser, doc2, (int)strlen(doc2), 1);
328   XML_ParserFree(parser);
329   free(mydata);
330   if (result != XML_STATUS_ERROR)
331     fail("Stopping the parser did not work as expected");
332 }
333 END_TEST
334 
START_TEST(test_misc_deny_internal_entity_closing_doctype_issue_317)335 START_TEST(test_misc_deny_internal_entity_closing_doctype_issue_317) {
336   const char *const inputOne
337       = "<!DOCTYPE d [\n"
338         "<!ENTITY % element_d '<!ELEMENT d (#PCDATA)*>'>\n"
339         "%element_d;\n"
340         "<!ENTITY % e ']><d/>'>\n"
341         "\n"
342         "%e;";
343   const char *const inputTwo
344       = "<!DOCTYPE d [\n"
345         "<!ENTITY % element_d '<!ELEMENT d (#PCDATA)*>'>\n"
346         "%element_d;\n"
347         "<!ENTITY % e1 ']><d/>'><!ENTITY % e2 '&#37;e1;'>\n"
348         "\n"
349         "%e2;";
350   const char *const inputThree
351       = "<!DOCTYPE d [\n"
352         "<!ENTITY % element_d '<!ELEMENT d (#PCDATA)*>'>\n"
353         "%element_d;\n"
354         "<!ENTITY % e ']><d'>\n"
355         "\n"
356         "%e;/>";
357   const char *const inputIssue317
358       = "<!DOCTYPE doc [\n"
359         "<!ENTITY % element_doc '<!ELEMENT doc (#PCDATA)*>'>\n"
360         "%element_doc;\n"
361         "<!ENTITY % foo ']>\n"
362         "<doc>Hell<oc (#PCDATA)*>'>\n"
363         "%foo;\n"
364         "]>\n"
365         "<doc>Hello, world</dVc>";
366 
367   const char *const inputs[] = {inputOne, inputTwo, inputThree, inputIssue317};
368   const XML_Bool suspendOrNot[] = {XML_FALSE, XML_TRUE};
369   size_t inputIndex = 0;
370 
371   for (; inputIndex < sizeof(inputs) / sizeof(inputs[0]); inputIndex++) {
372     for (size_t suspendOrNotIndex = 0;
373          suspendOrNotIndex < sizeof(suspendOrNot) / sizeof(suspendOrNot[0]);
374          suspendOrNotIndex++) {
375       const char *const input = inputs[inputIndex];
376       const XML_Bool suspend = suspendOrNot[suspendOrNotIndex];
377       if (suspend && (g_chunkSize > 0)) {
378         // We cannot use _XML_Parse_SINGLE_BYTES below due to suspension, and
379         // so chunk sizes >0 would only repeat the very same test
380         // due to use of plain XML_Parse; we are saving upon that runtime:
381         return;
382       }
383 
384       set_subtest("[input=%d suspend=%s] %s", (int)inputIndex,
385                   suspend ? "true" : "false", input);
386       XML_Parser parser;
387       enum XML_Status parseResult;
388       int setParamEntityResult;
389       XML_Size lineNumber;
390       XML_Size columnNumber;
391 
392       parser = XML_ParserCreate(NULL);
393       setParamEntityResult
394           = XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
395       if (setParamEntityResult != 1)
396         fail("Failed to set XML_PARAM_ENTITY_PARSING_ALWAYS.");
397 
398       if (suspend) {
399         XML_SetUserData(parser, parser);
400         XML_SetElementDeclHandler(parser, suspend_after_element_declaration);
401       }
402 
403       if (suspend) {
404         // can't use SINGLE_BYTES here, because it'll return early on
405         // suspension, and we won't know exactly how much input we actually
406         // managed to give Expat.
407         parseResult = XML_Parse(parser, input, (int)strlen(input), 0);
408 
409         while (parseResult == XML_STATUS_SUSPENDED) {
410           parseResult = XML_ResumeParser(parser);
411         }
412 
413         if (parseResult != XML_STATUS_ERROR) {
414           // can't use SINGLE_BYTES here, because it'll return early on
415           // suspension, and we won't know exactly how much input we actually
416           // managed to give Expat.
417           parseResult = XML_Parse(parser, "", 0, 1);
418         }
419 
420         while (parseResult == XML_STATUS_SUSPENDED) {
421           parseResult = XML_ResumeParser(parser);
422         }
423       } else {
424         parseResult
425             = _XML_Parse_SINGLE_BYTES(parser, input, (int)strlen(input), 0);
426 
427         if (parseResult != XML_STATUS_ERROR) {
428           parseResult = _XML_Parse_SINGLE_BYTES(parser, "", 0, 1);
429         }
430       }
431 
432       if (parseResult != XML_STATUS_ERROR) {
433         fail("Parsing was expected to fail but succeeded.");
434       }
435 
436       if (XML_GetErrorCode(parser) != XML_ERROR_INVALID_TOKEN)
437         fail("Error code does not match XML_ERROR_INVALID_TOKEN");
438 
439       lineNumber = XML_GetCurrentLineNumber(parser);
440       if (lineNumber != 6)
441         fail("XML_GetCurrentLineNumber does not work as expected.");
442 
443       columnNumber = XML_GetCurrentColumnNumber(parser);
444       if (columnNumber != 0)
445         fail("XML_GetCurrentColumnNumber does not work as expected.");
446 
447       XML_ParserFree(parser);
448     }
449   }
450 }
451 END_TEST
452 
START_TEST(test_misc_tag_mismatch_reset_leak)453 START_TEST(test_misc_tag_mismatch_reset_leak) {
454 #ifdef XML_NS
455   const char *const text = "<open xmlns='https://namespace1.test'></close>";
456   XML_Parser parser = XML_ParserCreateNS(NULL, XCS('\n'));
457 
458   if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
459       != XML_STATUS_ERROR)
460     fail("Call to parse was expected to fail");
461   if (XML_GetErrorCode(parser) != XML_ERROR_TAG_MISMATCH)
462     fail("Call to parse was expected to fail from a closing tag mismatch");
463 
464   XML_ParserReset(parser, NULL);
465 
466   if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
467       != XML_STATUS_ERROR)
468     fail("Call to parse was expected to fail");
469   if (XML_GetErrorCode(parser) != XML_ERROR_TAG_MISMATCH)
470     fail("Call to parse was expected to fail from a closing tag mismatch");
471 
472   XML_ParserFree(parser);
473 #endif
474 }
475 END_TEST
476 
START_TEST(test_misc_create_external_entity_parser_with_null_context)477 START_TEST(test_misc_create_external_entity_parser_with_null_context) {
478   // With XML_DTD undefined, the only supported case of external entities
479   // is pattern "<!ENTITY entity123 SYSTEM 'filename123'>". A NULL context
480   // was causing a segfault through a null pointer dereference in function
481   // setContext, previously.
482   XML_Parser parser = XML_ParserCreate(NULL);
483   XML_Parser ext_parser = XML_ExternalEntityParserCreate(parser, NULL, NULL);
484 #ifdef XML_DTD
485   assert_true(ext_parser != NULL);
486   XML_ParserFree(ext_parser);
487 #else
488   assert_true(ext_parser == NULL);
489 #endif /* XML_DTD */
490   XML_ParserFree(parser);
491 }
492 END_TEST
493 
START_TEST(test_misc_general_entities_support)494 START_TEST(test_misc_general_entities_support) {
495   const char *const doc
496       = "<!DOCTYPE r [\n"
497         "<!ENTITY e1 'v1'>\n"
498         "<!ENTITY e2 SYSTEM 'v2'>\n"
499         "]>\n"
500         "<r a1='[&e1;]'>[&e1;][&e2;][&amp;&apos;&gt;&lt;&quot;]</r>";
501 
502   CharData storage;
503   CharData_Init(&storage);
504 
505   XML_Parser parser = XML_ParserCreate(NULL);
506   XML_SetUserData(parser, &storage);
507   XML_SetStartElementHandler(parser, accumulate_start_element);
508   XML_SetExternalEntityRefHandler(parser,
509                                   external_entity_failer__if_not_xml_ge);
510   XML_SetEntityDeclHandler(parser, accumulate_entity_decl);
511   XML_SetCharacterDataHandler(parser, accumulate_characters);
512 
513   if (_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc), XML_TRUE)
514       != XML_STATUS_OK) {
515     xml_failure(parser);
516   }
517 
518   XML_ParserFree(parser);
519 
520   CharData_CheckXMLChars(&storage,
521   /* clang-format off */
522 #if XML_GE == 1
523                          XCS("e1=v1\n")
524                          XCS("e2=(null)\n")
525                          XCS("(r(a1=[v1]))\n")
526                          XCS("[v1][][&'><\"]")
527 #else
528                          XCS("e1=&amp;e1;\n")
529                          XCS("e2=(null)\n")
530                          XCS("(r(a1=[&e1;]))\n")
531                          XCS("[&e1;][&e2;][&'><\"]")
532 #endif
533   );
534   /* clang-format on */
535 }
536 END_TEST
537 
538 static void XMLCALL
resumable_stopping_character_handler(void * userData,const XML_Char * s,int len)539 resumable_stopping_character_handler(void *userData, const XML_Char *s,
540                                      int len) {
541   UNUSED_P(s);
542   UNUSED_P(len);
543   XML_Parser parser = (XML_Parser)userData;
544   XML_StopParser(parser, XML_TRUE);
545 }
546 
547 // NOTE: This test needs active LeakSanitizer to be of actual use
START_TEST(test_misc_char_handler_stop_without_leak)548 START_TEST(test_misc_char_handler_stop_without_leak) {
549   const char *const data
550       = "<!DOCTYPE t1[<!ENTITY e1 'angle<'><!ENTITY e2 '&e1;'>]><t1>&e2;";
551   XML_Parser parser = XML_ParserCreate(NULL);
552   assert_true(parser != NULL);
553   XML_SetUserData(parser, parser);
554   XML_SetCharacterDataHandler(parser, resumable_stopping_character_handler);
555   _XML_Parse_SINGLE_BYTES(parser, data, (int)strlen(data), XML_FALSE);
556   XML_ParserFree(parser);
557 }
558 END_TEST
559 
START_TEST(test_misc_resumeparser_not_crashing)560 START_TEST(test_misc_resumeparser_not_crashing) {
561   XML_Parser parser = XML_ParserCreate(NULL);
562   XML_GetBuffer(parser, 1);
563   XML_StopParser(parser, /*resumable=*/XML_TRUE);
564   XML_ResumeParser(parser); // could crash here, previously
565   XML_ParserFree(parser);
566 }
567 END_TEST
568 
START_TEST(test_misc_stopparser_rejects_unstarted_parser)569 START_TEST(test_misc_stopparser_rejects_unstarted_parser) {
570   const XML_Bool cases[] = {XML_TRUE, XML_FALSE};
571   for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
572     const XML_Bool resumable = cases[i];
573     XML_Parser parser = XML_ParserCreate(NULL);
574     assert_true(XML_GetErrorCode(parser) == XML_ERROR_NONE);
575     assert_true(XML_StopParser(parser, resumable) == XML_STATUS_ERROR);
576     assert_true(XML_GetErrorCode(parser) == XML_ERROR_NOT_STARTED);
577     XML_ParserFree(parser);
578   }
579 }
580 END_TEST
581 
582 /* Adaptation of accumulate_characters that takes ExtHdlrData input to work with
583  * test_renter_loop_finite_content below */
584 void XMLCALL
accumulate_characters_ext_handler(void * userData,const XML_Char * s,int len)585 accumulate_characters_ext_handler(void *userData, const XML_Char *s, int len) {
586   ExtHdlrData *const test_data = (ExtHdlrData *)userData;
587   CharData_AppendXMLChars(test_data->storage, s, len);
588 }
589 
590 /* Test that internalEntityProcessor does not re-enter forever;
591  * based on files tests/xmlconf/xmltest/valid/ext-sa/012.{xml,ent} */
START_TEST(test_renter_loop_finite_content)592 START_TEST(test_renter_loop_finite_content) {
593   CharData storage;
594   CharData_Init(&storage);
595   const char *const text = "<!DOCTYPE doc [\n"
596                            "<!ENTITY e1 '&e2;'>\n"
597                            "<!ENTITY e2 '&e3;'>\n"
598                            "<!ENTITY e3 SYSTEM '012.ent'>\n"
599                            "<!ENTITY e4 '&e5;'>\n"
600                            "<!ENTITY e5 '(e5)'>\n"
601                            "<!ELEMENT doc (#PCDATA)>\n"
602                            "]>\n"
603                            "<doc>&e1;</doc>\n";
604   ExtHdlrData test_data = {"&e4;\n", external_entity_null_loader, &storage};
605   const XML_Char *const expected = XCS("(e5)\n");
606 
607   XML_Parser parser = XML_ParserCreate(NULL);
608   assert_true(parser != NULL);
609   XML_SetUserData(parser, &test_data);
610   XML_SetExternalEntityRefHandler(parser, external_entity_oneshot_loader);
611   XML_SetCharacterDataHandler(parser, accumulate_characters_ext_handler);
612   if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
613       == XML_STATUS_ERROR)
614     xml_failure(parser);
615 
616   CharData_CheckXMLChars(&storage, expected);
617   XML_ParserFree(parser);
618 }
619 END_TEST
620 
621 // Inspired by function XML_OriginalString of Perl's XML::Parser
622 static char *
dup_original_string(XML_Parser parser)623 dup_original_string(XML_Parser parser) {
624   const int byte_count = XML_GetCurrentByteCount(parser);
625 
626   assert_true(byte_count >= 0);
627 
628   int offset = -1;
629   int size = -1;
630 
631   const char *const context = XML_GetInputContext(parser, &offset, &size);
632 
633 #if XML_CONTEXT_BYTES > 0
634   assert_true(context != NULL);
635   assert_true(offset >= 0);
636   assert_true(size >= 0);
637   return portable_strndup(context + offset, byte_count);
638 #else
639   assert_true(context == NULL);
640   return NULL;
641 #endif
642 }
643 
644 static void
on_characters_issue_980(void * userData,const XML_Char * s,int len)645 on_characters_issue_980(void *userData, const XML_Char *s, int len) {
646   (void)s;
647   (void)len;
648   XML_Parser parser = (XML_Parser)userData;
649 
650   char *const original_string = dup_original_string(parser);
651 
652 #if XML_CONTEXT_BYTES > 0
653   assert_true(original_string != NULL);
654   assert_true(strcmp(original_string, "&draft.day;") == 0);
655   free(original_string);
656 #else
657   assert_true(original_string == NULL);
658 #endif
659 }
660 
START_TEST(test_misc_expected_event_ptr_issue_980)661 START_TEST(test_misc_expected_event_ptr_issue_980) {
662   // NOTE: This is a tiny subset of sample "REC-xml-19980210.xml"
663   //       from Perl's XML::Parser
664   const char *const doc = "<!DOCTYPE day [\n"
665                           "  <!ENTITY draft.day '10'>\n"
666                           "]>\n"
667                           "<day>&draft.day;</day>\n";
668 
669   XML_Parser parser = XML_ParserCreate(NULL);
670   XML_SetUserData(parser, parser);
671   XML_SetCharacterDataHandler(parser, on_characters_issue_980);
672 
673   assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
674                                       /*isFinal=*/XML_TRUE)
675               == XML_STATUS_OK);
676 
677   XML_ParserFree(parser);
678 }
679 END_TEST
680 
681 void
make_miscellaneous_test_case(Suite * s)682 make_miscellaneous_test_case(Suite *s) {
683   TCase *tc_misc = tcase_create("miscellaneous tests");
684 
685   suite_add_tcase(s, tc_misc);
686   tcase_add_checked_fixture(tc_misc, NULL, basic_teardown);
687 
688   tcase_add_test(tc_misc, test_misc_alloc_create_parser);
689   tcase_add_test(tc_misc, test_misc_alloc_create_parser_with_encoding);
690   tcase_add_test(tc_misc, test_misc_null_parser);
691   tcase_add_test(tc_misc, test_misc_error_string);
692   tcase_add_test(tc_misc, test_misc_version);
693   tcase_add_test(tc_misc, test_misc_features);
694   tcase_add_test(tc_misc, test_misc_attribute_leak);
695   tcase_add_test(tc_misc, test_misc_utf16le);
696   tcase_add_test(tc_misc, test_misc_stop_during_end_handler_issue_240_1);
697   tcase_add_test(tc_misc, test_misc_stop_during_end_handler_issue_240_2);
698   tcase_add_test__ifdef_xml_dtd(
699       tc_misc, test_misc_deny_internal_entity_closing_doctype_issue_317);
700   tcase_add_test(tc_misc, test_misc_tag_mismatch_reset_leak);
701   tcase_add_test(tc_misc,
702                  test_misc_create_external_entity_parser_with_null_context);
703   tcase_add_test(tc_misc, test_misc_general_entities_support);
704   tcase_add_test(tc_misc, test_misc_char_handler_stop_without_leak);
705   tcase_add_test(tc_misc, test_misc_resumeparser_not_crashing);
706   tcase_add_test(tc_misc, test_misc_stopparser_rejects_unstarted_parser);
707   tcase_add_test__if_xml_ge(tc_misc, test_renter_loop_finite_content);
708   tcase_add_test(tc_misc, test_misc_expected_event_ptr_issue_980);
709 }
710