From e9cf8bfe72423c0eee4fd79b161430f892a9f9e1 Mon Sep 17 00:00:00 2001 From: skdas20 Date: Wed, 2 Sep 2026 12:40:51 +0000 Subject: [PATCH] gh-156796: Finalize the parser of an external entity in xml.sax ExpatParser.close() returned early whenever _entity_stack was not empty, so feed(b"", isFinal=True) was never called for the parser created by external_entity_ref(). Expat therefore never checked the entity's input for completeness, and an external entity whose content is not well-formed was silently accepted. Errors expat detects while feeding data, such as a mismatched tag, were still reported; only those detectable at the end of the input, such as an unclosed element, were lost. Finalize the entity's parser in that case, while still leaving the document open, since the enclosing parse is what the early return was there to protect. --- Lib/test/test_sax.py | 20 +++++++++++++++++++ Lib/xml/sax/expatreader.py | 11 +++++++--- ...-09-02-00-00-00.gh-issue-156796.k7Qw2P.rst | 4 ++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-02-00-00-00.gh-issue-156796.k7Qw2P.rst diff --git a/Lib/test/test_sax.py b/Lib/test/test_sax.py index 29babd7bf6996a..2b89569a2857dc 100644 --- a/Lib/test/test_sax.py +++ b/Lib/test/test_sax.py @@ -1039,6 +1039,26 @@ def test_expat_entityresolver_enabled(self): self.assertEqual(result.getvalue(), start + b"") + def test_expat_entityresolver_not_well_formed(self): + # gh-156796: the parser of an external entity was never finalized, + # so errors only detectable at the end of its input, such as an + # unclosed element, were silently ignored. + class NotWellFormedEntityResolver: + def resolveEntity(self, publicId, systemId): + inpsrc = InputSource() + inpsrc.setByteStream(BytesIO(b"")) + return inpsrc + + parser = create_parser() + parser.setFeature(feature_external_ges, True) + parser.setEntityResolver(NotWellFormedEntityResolver()) + parser.setContentHandler(XMLGenerator(BytesIO())) + + with self.assertRaises(SAXParseException): + parser.feed(']>' + '&test;') + parser.close() + def test_expat_entityresolver_default(self): parser = create_parser() self.assertEqual(parser.getFeature(feature_external_ges), False) diff --git a/Lib/xml/sax/expatreader.py b/Lib/xml/sax/expatreader.py index 37b1add2848487..1ce3b6b5e584fc 100644 --- a/Lib/xml/sax/expatreader.py +++ b/Lib/xml/sax/expatreader.py @@ -238,9 +238,14 @@ def _close_source(self): file.close() def close(self): - if (self._entity_stack or self._parser is None or - isinstance(self._parser, _ClosedParser)): - # If we are completing an external entity, do nothing here + if self._parser is None or isinstance(self._parser, _ClosedParser): + return + if self._entity_stack: + # We are completing an external entity. Finalize its parser so + # that errors which are only detectable at the end of the input, + # such as an unclosed element, are reported, but do not end the + # document: the enclosing parse is still in progress. + self.feed(b"", isFinal=True) return try: self.feed(b"", isFinal=True) diff --git a/Misc/NEWS.d/next/Library/2026-09-02-00-00-00.gh-issue-156796.k7Qw2P.rst b/Misc/NEWS.d/next/Library/2026-09-02-00-00-00.gh-issue-156796.k7Qw2P.rst new file mode 100644 index 00000000000000..d5aadccaf3c37d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-02-00-00-00.gh-issue-156796.k7Qw2P.rst @@ -0,0 +1,4 @@ +Fix :mod:`xml.sax` not reporting an external entity whose content is not +well-formed. The parser created for the entity was never finalized, so errors +which are only detectable at the end of its input, such as an unclosed +element, were silently ignored.