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