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