diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index c52c640bd4b26f8..ef2334c9c450c13 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -834,6 +834,61 @@ def handler(text): self.assertRaises(SpecificException, parser.Parse, xml.encode('iso8859')) + +class DefaultHandlerExceptionTest(unittest.TestCase): + # In a document that is not UTF-8, Expat passes a long piece of text such + # as this comment to the default handler in chunks of at most 1024 bytes, + # re-reading the handler between chunks. Clearing the handler from within + # the callback, by raising or by setting it to None, must not crash. + xml = ("" + % ('a' * 2000)).encode('iso8859') + + class SpecificException(Exception): + pass + + def check(self, name): + def handler(text): + if text.startswith('" + % ('a' * 2000)).encode('iso8859') + self.assertRaises(self.SpecificException, parser.Parse, xml, True) + + class ChardataBufferTest(unittest.TestCase): """ test setting of chardata buffer size diff --git a/Misc/NEWS.d/next/Library/2026-11-01-05-05-38.gh-issue-161060.YOZsVl.rst b/Misc/NEWS.d/next/Library/2026-11-01-05-05-38.gh-issue-161060.YOZsVl.rst new file mode 100644 index 000000000000000..487d2552ce0c873 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-01-05-05-38.gh-issue-161060.YOZsVl.rst @@ -0,0 +1,5 @@ +Fix a crash in :mod:`xml.parsers.expat` when the +:meth:`~xml.parsers.expat.xmlparser.DefaultHandler` or +:meth:`~xml.parsers.expat.xmlparser.DefaultHandlerExpand` callback raises an +exception or unsets the handler while a token longer than 1024 bytes, such as +a comment, is being reported from a document that is not encoded in UTF-8. diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c index fb9d333babf70b4..7c633b3fea5e766 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -293,10 +293,13 @@ error_external_entity_ref_handler(XML_Parser parser, /* Dummy character data handler used when an error (exception) has been detected, and the actual parsing can be terminated early. - This is needed since character data handler can't be safely removed - from within the character data handler, but can be replaced. It is - used only from the character data handler trampoline, and must be - used right after `flag_error()` is called. */ + This is needed since the character data and default handlers can't be + safely removed from within a callback, only replaced: Expat may call + them several times for one piece of text (a long comment in a document + that is not UTF-8, for example). The default handler has the same + signature, so this function serves for both. clear_handlers() installs + it after `flag_error()`, and the handler setter installs it when a + handler is set to None from within a callback. */ static void noop_character_data_handler(void *userData, const XML_Char *data, int len) { @@ -1838,8 +1841,14 @@ xmlparse_handler_setter(PyObject *op, PyObject *v, void *closure) active, so Expat does a little more work than it otherwise would, but that's really an odd case. A more elaborate system of handlers and state could remove the - C handler more effectively. */ - if (handlernum == CharacterData && self->in_callback) { + C handler more effectively. The same applies to the + default handler, which Expat can call several times for one + piece of text, such as a long comment in a document that is + not UTF-8. */ + if (self->in_callback + && (handlernum == CharacterData + || handlernum == Default + || handlernum == DefaultHandlerExpand)) { c_handler = (xmlhandler)noop_character_data_handler; } v = NULL; @@ -2786,7 +2795,16 @@ clear_handlers(xmlparseobject *self, int initial) } else { Py_CLEAR(self->handlers[i]); - CALL_XML_HANDLER_SETTER(&handler_info[i], self->itself, NULL); + xmlhandler c_handler = NULL; + if (i == Default || i == DefaultHandlerExpand) { + /* Like the character data handler, the default handler + can't be removed from within a callback: for a document + that is not UTF-8, Expat passes a long piece of text to + it in several calls and re-reads the handler before each + one (reportDefault() in xmlparse.c). */ + c_handler = (xmlhandler)noop_character_data_handler; + } + CALL_XML_HANDLER_SETTER(&handler_info[i], self->itself, c_handler); } } }