mirror of
https://github.com/python/cpython.git
synced 2025-11-20 02:50:14 +00:00
bpo-17239: Disable external entities in SAX parser (GH-9217)
The SAX parser no longer processes general external entities by default to increase security. Before, the parser created network connections to fetch remote files or loaded local files from the file system for DTD and entities. Signed-off-by: Christian Heimes <christian@python.org> https://bugs.python.org/issue17239
This commit is contained in:
parent
9fb051f032
commit
17b1d5d4e3
9 changed files with 120 additions and 5 deletions
|
|
@ -3,6 +3,7 @@ import unittest
|
|||
import xml.sax
|
||||
|
||||
from xml.sax.xmlreader import AttributesImpl
|
||||
from xml.sax.handler import feature_external_ges
|
||||
from xml.dom import pulldom
|
||||
|
||||
from test.support import findfile
|
||||
|
|
@ -166,6 +167,12 @@ class PullDOMTestCase(unittest.TestCase):
|
|||
# This should have returned 'END_ELEMENT'.
|
||||
self.assertEqual(parser[-1][0], pulldom.START_DOCUMENT)
|
||||
|
||||
def test_external_ges_default(self):
|
||||
parser = pulldom.parseString(SMALL_SAMPLE)
|
||||
saxparser = parser.parser
|
||||
ges = saxparser.getFeature(feature_external_ges)
|
||||
self.assertEqual(ges, False)
|
||||
|
||||
|
||||
class ThoroughTestCase(unittest.TestCase):
|
||||
"""Test the hard-to-reach parts of pulldom."""
|
||||
|
|
|
|||
|
|
@ -13,13 +13,14 @@ except SAXReaderNotAvailable:
|
|||
from xml.sax.saxutils import XMLGenerator, escape, unescape, quoteattr, \
|
||||
XMLFilterBase, prepare_input_source
|
||||
from xml.sax.expatreader import create_parser
|
||||
from xml.sax.handler import feature_namespaces
|
||||
from xml.sax.handler import feature_namespaces, feature_external_ges
|
||||
from xml.sax.xmlreader import InputSource, AttributesImpl, AttributesNSImpl
|
||||
from io import BytesIO, StringIO
|
||||
import codecs
|
||||
import gc
|
||||
import os.path
|
||||
import shutil
|
||||
from urllib.error import URLError
|
||||
from test import support
|
||||
from test.support import findfile, run_unittest, TESTFN
|
||||
|
||||
|
|
@ -911,6 +912,18 @@ class ExpatReaderTest(XmlTestBase):
|
|||
def unparsedEntityDecl(self, name, publicId, systemId, ndata):
|
||||
self._entities.append((name, publicId, systemId, ndata))
|
||||
|
||||
|
||||
class TestEntityRecorder:
|
||||
def __init__(self):
|
||||
self.entities = []
|
||||
|
||||
def resolveEntity(self, publicId, systemId):
|
||||
self.entities.append((publicId, systemId))
|
||||
source = InputSource()
|
||||
source.setPublicId(publicId)
|
||||
source.setSystemId(systemId)
|
||||
return source
|
||||
|
||||
def test_expat_dtdhandler(self):
|
||||
parser = create_parser()
|
||||
handler = self.TestDTDHandler()
|
||||
|
|
@ -927,6 +940,32 @@ class ExpatReaderTest(XmlTestBase):
|
|||
[("GIF", "-//CompuServe//NOTATION Graphics Interchange Format 89a//EN", None)])
|
||||
self.assertEqual(handler._entities, [("img", None, "expat.gif", "GIF")])
|
||||
|
||||
def test_expat_external_dtd_enabled(self):
|
||||
parser = create_parser()
|
||||
parser.setFeature(feature_external_ges, True)
|
||||
resolver = self.TestEntityRecorder()
|
||||
parser.setEntityResolver(resolver)
|
||||
|
||||
with self.assertRaises(URLError):
|
||||
parser.feed(
|
||||
'<!DOCTYPE external SYSTEM "unsupported://non-existing">\n'
|
||||
)
|
||||
self.assertEqual(
|
||||
resolver.entities, [(None, 'unsupported://non-existing')]
|
||||
)
|
||||
|
||||
def test_expat_external_dtd_default(self):
|
||||
parser = create_parser()
|
||||
resolver = self.TestEntityRecorder()
|
||||
parser.setEntityResolver(resolver)
|
||||
|
||||
parser.feed(
|
||||
'<!DOCTYPE external SYSTEM "unsupported://non-existing">\n'
|
||||
)
|
||||
parser.feed('<doc />')
|
||||
parser.close()
|
||||
self.assertEqual(resolver.entities, [])
|
||||
|
||||
# ===== EntityResolver support
|
||||
|
||||
class TestEntityResolver:
|
||||
|
|
@ -936,8 +975,9 @@ class ExpatReaderTest(XmlTestBase):
|
|||
inpsrc.setByteStream(BytesIO(b"<entity/>"))
|
||||
return inpsrc
|
||||
|
||||
def test_expat_entityresolver(self):
|
||||
def test_expat_entityresolver_enabled(self):
|
||||
parser = create_parser()
|
||||
parser.setFeature(feature_external_ges, True)
|
||||
parser.setEntityResolver(self.TestEntityResolver())
|
||||
result = BytesIO()
|
||||
parser.setContentHandler(XMLGenerator(result))
|
||||
|
|
@ -951,6 +991,22 @@ class ExpatReaderTest(XmlTestBase):
|
|||
self.assertEqual(result.getvalue(), start +
|
||||
b"<doc><entity></entity></doc>")
|
||||
|
||||
def test_expat_entityresolver_default(self):
|
||||
parser = create_parser()
|
||||
self.assertEqual(parser.getFeature(feature_external_ges), False)
|
||||
parser.setEntityResolver(self.TestEntityResolver())
|
||||
result = BytesIO()
|
||||
parser.setContentHandler(XMLGenerator(result))
|
||||
|
||||
parser.feed('<!DOCTYPE doc [\n')
|
||||
parser.feed(' <!ENTITY test SYSTEM "whatever">\n')
|
||||
parser.feed(']>\n')
|
||||
parser.feed('<doc>&test;</doc>')
|
||||
parser.close()
|
||||
|
||||
self.assertEqual(result.getvalue(), start +
|
||||
b"<doc></doc>")
|
||||
|
||||
# ===== Attributes support
|
||||
|
||||
class AttrGatherer(ContentHandler):
|
||||
|
|
|
|||
|
|
@ -91,6 +91,12 @@ ENTITY_XML = """\
|
|||
<document>&entity;</document>
|
||||
"""
|
||||
|
||||
EXTERNAL_ENTITY_XML = """\
|
||||
<!DOCTYPE points [
|
||||
<!ENTITY entity SYSTEM "file:///non-existing-file.xml">
|
||||
]>
|
||||
<document>&entity;</document>
|
||||
"""
|
||||
|
||||
def checkwarnings(*filters, quiet=False):
|
||||
def decorator(test):
|
||||
|
|
@ -861,6 +867,13 @@ class ElementTreeTest(unittest.TestCase):
|
|||
root = parser.close()
|
||||
self.serialize_check(root, '<document>text</document>')
|
||||
|
||||
# 4) external (SYSTEM) entity
|
||||
|
||||
with self.assertRaises(ET.ParseError) as cm:
|
||||
ET.XML(EXTERNAL_ENTITY_XML)
|
||||
self.assertEqual(str(cm.exception),
|
||||
'undefined entity &entity;: line 4, column 10')
|
||||
|
||||
def test_namespace(self):
|
||||
# Test namespace issues.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue