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 '%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;][&'><"]</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=&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