Skip to content

Commit e9cf8bf

Browse files
committed
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.
1 parent e7c93b7 commit e9cf8bf

3 files changed

Lines changed: 32 additions & 3 deletions

File tree

Lib/test/test_sax.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,26 @@ def test_expat_entityresolver_enabled(self):
10391039
self.assertEqual(result.getvalue(), start +
10401040
b"<doc><entity></entity></doc>")
10411041

1042+
def test_expat_entityresolver_not_well_formed(self):
1043+
# gh-156796: the parser of an external entity was never finalized,
1044+
# so errors only detectable at the end of its input, such as an
1045+
# unclosed element, were silently ignored.
1046+
class NotWellFormedEntityResolver:
1047+
def resolveEntity(self, publicId, systemId):
1048+
inpsrc = InputSource()
1049+
inpsrc.setByteStream(BytesIO(b"<entity>"))
1050+
return inpsrc
1051+
1052+
parser = create_parser()
1053+
parser.setFeature(feature_external_ges, True)
1054+
parser.setEntityResolver(NotWellFormedEntityResolver())
1055+
parser.setContentHandler(XMLGenerator(BytesIO()))
1056+
1057+
with self.assertRaises(SAXParseException):
1058+
parser.feed('<!DOCTYPE doc [<!ENTITY test SYSTEM "whatever">]>'
1059+
'<doc>&test;</doc>')
1060+
parser.close()
1061+
10421062
def test_expat_entityresolver_default(self):
10431063
parser = create_parser()
10441064
self.assertEqual(parser.getFeature(feature_external_ges), False)

Lib/xml/sax/expatreader.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,14 @@ def _close_source(self):
238238
file.close()
239239

240240
def close(self):
241-
if (self._entity_stack or self._parser is None or
242-
isinstance(self._parser, _ClosedParser)):
243-
# If we are completing an external entity, do nothing here
241+
if self._parser is None or isinstance(self._parser, _ClosedParser):
242+
return
243+
if self._entity_stack:
244+
# We are completing an external entity. Finalize its parser so
245+
# that errors which are only detectable at the end of the input,
246+
# such as an unclosed element, are reported, but do not end the
247+
# document: the enclosing parse is still in progress.
248+
self.feed(b"", isFinal=True)
244249
return
245250
try:
246251
self.feed(b"", isFinal=True)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :mod:`xml.sax` not reporting an external entity whose content is not
2+
well-formed. The parser created for the entity was never finalized, so errors
3+
which are only detectable at the end of its input, such as an unclosed
4+
element, were silently ignored.

0 commit comments

Comments
 (0)