xml.etree.ElementTree ships with Python, uses Expat 1,381 (Expat) and shares lxml 3,063 's basic API, so code moves between them; the differences are at the edges. This script runs four tests through both, two of them hostile inputs: an external entity that reads a local file (XXE) and a "billion laughs" entity bomb:
import xml.etree.ElementTree as ET
from lxml import etree
roots = {ET: ET.parse("booknest-catalog.xml").getroot(),
etree: etree.parse("booknest-catalog.xml").getroot()}
xxe = b'<!DOCTYPE b [<!ENTITY x SYSTEM "file:///etc/hostname">]><b>&x;</b>'
levels = b"".join(b'<!ENTITY %c "%s">' % (c, b"&%c;" % (c - 1) * 10) for c in range(98, 106))
bomb = b'<!DOCTYPE b [<!ENTITY a "' + b"x" * 100 + b'">' + levels + b"]><b>&i;</b>"
tests = {
"XPath": lambda r, m: len(r.xpath("book[supply/price > 20]") if m is etree
else r.findall("book[supply/price > 20]")),
"parent": lambda r, m: r.find("book").getparent().tag,
"XXE": lambda r, m: m.fromstring(xxe).text,
"entity bomb": lambda r, m: len(m.fromstring(bomb).text),
}
for test, fn in tests.items():
for mod, name in ((ET, "ET"), (etree, "lxml")):
try:
result = fn(roots[mod], mod)
except Exception as e:
result = f"{type(e).__name__}: {str(e)[:52]}"
print(f"{test:11} {name:4} -> {result}")Output
XPath ET -> SyntaxError: invalid predicate XPath lxml -> 3 parent ET -> AttributeError: 'xml.etree.ElementTree.Element' object has no attrib parent lxml -> catalog XXE ET -> ParseError: undefined entity &x;: line 1, column 59 XXE lxml -> XMLSyntaxError: Entity 'x' not defined, line 1, column 63 (<string>, entity bomb ET -> ParseError: limit on input amplification factor (from DTD and en entity bomb lxml -> XMLSyntaxError: Maximum entity amplification factor exceeded, see xm
| Feature | ElementTree | lxml |
|---|---|---|
| Paths | ElementPath subset | Full XPath 1.0 |
| Parent, source line | No | getparent(), sourceline |
| XSLT, XSD, RELAX NG | No | XSLT 1.0, XSD 1.0, RELAX NG |
| External entities | Not loaded | Not loaded by default |
Both refused the hostile inputs: current libxml2 3,427 and Expat cap entity amplification, and neither fetches external entities by default. Older versions did, so pin current releases or use defusedxml, and never enable resolve_entities or load_dtd for outside data.