Skip to content

Commit c986d2d

Browse files
authored
fix: fail closed when entities disabled
1 parent 3d4d2d3 commit c986d2d

File tree

2 files changed

+33
-20
lines changed

2 files changed

+33
-20
lines changed

tests/test_xmltodict.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ def force_list(path, key, value):
410410
assert parse(xml, force_list=force_list, dict_constructor=dict) == expectedResult
411411

412412

413-
def test_disable_entities_true_ignores_xmlbomb():
413+
def test_disable_entities_true_rejects_xmlbomb():
414414
xml = """
415415
<!DOCTYPE xmlbomb [
416416
<!ENTITY a "1234567890" >
@@ -419,13 +419,8 @@ def test_disable_entities_true_ignores_xmlbomb():
419419
]>
420420
<bomb>&c;</bomb>
421421
"""
422-
expectedResult = {'bomb': None}
423-
try:
424-
parse_attempt = parse(xml, disable_entities=True)
425-
except expat.ExpatError:
426-
assert True
427-
else:
428-
assert parse_attempt == expectedResult
422+
with pytest.raises(expat.ExpatError, match="entities are disabled"):
423+
parse(xml, disable_entities=True)
429424

430425

431426
def test_disable_entities_false_returns_xmlbomb():
@@ -442,20 +437,15 @@ def test_disable_entities_false_returns_xmlbomb():
442437
assert parse(xml, disable_entities=False) == expectedResult
443438

444439

445-
def test_disable_entities_true_ignores_external_dtd():
440+
def test_disable_entities_true_rejects_external_dtd():
446441
xml = """
447442
<!DOCTYPE external [
448443
<!ENTITY ee SYSTEM "http://www.python.org/">
449444
]>
450445
<root>&ee;</root>
451446
"""
452-
expectedResult = {'root': None}
453-
try:
454-
parse_attempt = parse(xml, disable_entities=True)
455-
except expat.ExpatError:
456-
assert True
457-
else:
458-
assert parse_attempt == expectedResult
447+
with pytest.raises(expat.ExpatError, match="entities are disabled"):
448+
parse(xml, disable_entities=True)
459449

460450

461451
def test_disable_entities_true_attempts_external_dtd():
@@ -482,6 +472,16 @@ def raising_external_ref_handler(*args, **kwargs):
482472
expat.ParserCreate = ParserCreate
483473

484474

475+
def test_disable_entities_allows_comments_by_default():
476+
xml = """
477+
<a>
478+
<!-- ignored -->
479+
<b>1</b>
480+
</a>
481+
"""
482+
assert parse(xml) == {'a': {'b': '1'}}
483+
484+
485485
def test_comments():
486486
xml = """
487487
<a>

xmltodict.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -354,10 +354,23 @@ def parse(xml_input, encoding=None, expat=expat, process_namespaces=False,
354354
parser.CommentHandler = handler.comments
355355
parser.buffer_text = True
356356
if disable_entities:
357-
# Anything not handled ends up here and entities aren't expanded.
358-
parser.DefaultHandler = lambda x: None
359-
# Expects an integer return; zero means failure -> expat.ExpatError.
360-
parser.ExternalEntityRefHandler = lambda *x: 1
357+
def _forbid_entities(*_args, **_kwargs):
358+
raise expat.ExpatError("xmltodict.parse(): entities are disabled")
359+
360+
def _forbid_entities_default(text):
361+
if not text:
362+
return
363+
stripped = text.lstrip()
364+
if stripped.startswith('<!--'):
365+
return
366+
if stripped.startswith('<!') or stripped.startswith('&'):
367+
_forbid_entities()
368+
369+
# Reject DTD/entity constructs explicitly instead of ignoring them.
370+
parser.DefaultHandler = _forbid_entities_default
371+
parser.EntityDeclHandler = _forbid_entities
372+
parser.StartDoctypeDeclHandler = _forbid_entities
373+
parser.ExternalEntityRefHandler = _forbid_entities
361374
if hasattr(xml_input, 'read'):
362375
parser.ParseFile(xml_input)
363376
elif isgenerator(xml_input):

0 commit comments

Comments
 (0)