Skip to content

Commit e3010a5

Browse files
gh-157518: Validate the prefix in xml.etree.ElementTree.register_namespace()
ValueError is now raised for an invalid prefix, for the reserved xmlns prefix, and for the xml prefix bound to other namespace or other prefix bound to the XML namespace, as the documentation always promised. Only the reserved nsN prefixes were rejected before.
1 parent c215919 commit e3010a5

4 files changed

Lines changed: 65 additions & 2 deletions

File tree

Doc/library/xml.etree.elementtree.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,9 +739,15 @@ Functions
739739
*prefix* is a namespace prefix. *uri* is a namespace uri. Tags and
740740
attributes in this namespace will be serialized with the given prefix, if at
741741
all possible.
742+
:exc:`ValueError` is raised if *prefix* is not a valid prefix,
743+
is reserved (``xmlns`` and ``ns`` followed by digits),
744+
or if *prefix* is ``xml`` and *uri* is not the XML namespace or vice versa.
742745

743746
.. versionadded:: 3.2
744747

748+
.. versionchanged:: next
749+
Invalid and reserved prefixes are now rejected.
750+
745751

746752
.. function:: SubElement(parent, tag, /, attrib={}, **extra)
747753

Lib/test/test_xml_etree.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2848,6 +2848,36 @@ def test_bug_200709_register_namespace(self):
28482848
self.assertEqual(ET.tostring(e),
28492849
b'<dc:title xmlns:dc="http://purl.org/dc/elements/1.1/" />')
28502850

2851+
def test_register_namespace_invalid(self):
2852+
# gh-157518
2853+
xml_ns = 'http://www.w3.org/XML/1998/namespace'
2854+
xmlns_ns = 'http://www.w3.org/2000/xmlns/'
2855+
nsmap = ET.register_namespace._namespace_map
2856+
saved = dict(nsmap)
2857+
for prefix, uri in [
2858+
('ns0', 'u'), ('ns12', 'u'),
2859+
('xml', 'u'), ('foo', xml_ns),
2860+
('xmlns', 'u'), ('foo', xmlns_ns),
2861+
('a:b', 'u'), ('1', 'u'), ('a b', 'u'), ('a\xa0b', 'u'),
2862+
]:
2863+
with self.subTest(prefix=prefix, uri=uri):
2864+
with self.assertRaises(ValueError):
2865+
ET.register_namespace(prefix, uri)
2866+
# the registry is not changed
2867+
self.assertEqual(nsmap, saved)
2868+
for prefix, uri in [(1, 'u'), (b'a', 'u'), ('a', 1), ('a', None)]:
2869+
with self.subTest(prefix=prefix, uri=uri):
2870+
with self.assertRaises(TypeError):
2871+
ET.register_namespace(prefix, uri)
2872+
# the xml prefix can be registered for its namespace
2873+
ET.register_namespace('xml', xml_ns)
2874+
self.assertEqual(ET.register_namespace._namespace_map[xml_ns], 'xml')
2875+
# non-ASCII names are valid
2876+
self.addCleanup(ET.register_namespace._namespace_map.pop, 'u', None)
2877+
ET.register_namespace('\xe9', 'u')
2878+
self.assertEqual(ET.tostring(ET.Element('{u}a'), encoding='unicode'),
2879+
'<\xe9:a xmlns:\xe9="u" />')
2880+
28512881
def test_bug_200709_element_comment(self):
28522882
# Not sure if this can be fixed, really (since the serializer needs
28532883
# ET.Comment, not cET.comment).

Lib/xml/etree/ElementTree.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
import contextlib
9999
import weakref
100100

101+
from .. import is_valid_name
101102
from . import ElementPath
102103

103104

@@ -1029,6 +1030,29 @@ def _serialize_text(write, elem):
10291030
}
10301031

10311032

1033+
_XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"
1034+
_XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/"
1035+
1036+
def _check_prefix(prefix, uri):
1037+
# Check a namespace prefix and the namespace URI bound to it
1038+
# (see Namespaces in XML 1.0, 3 and 4). The empty prefix is allowed.
1039+
if not isinstance(prefix, str) or not isinstance(uri, str):
1040+
raise TypeError("namespace prefix and URI must be strings")
1041+
if re.match(r"ns\d+$", prefix):
1042+
raise ValueError("Prefix format reserved for internal use")
1043+
if prefix == "xml":
1044+
if uri != _XML_NAMESPACE:
1045+
raise ValueError("the 'xml' prefix can only be bound to "
1046+
"the XML namespace")
1047+
elif uri == _XML_NAMESPACE:
1048+
raise ValueError("the XML namespace can only be bound to "
1049+
"the 'xml' prefix")
1050+
elif prefix == "xmlns" or uri == _XMLNS_NAMESPACE:
1051+
raise ValueError("the 'xmlns' prefix cannot be bound")
1052+
elif prefix and (not is_valid_name(prefix) or ':' in prefix):
1053+
raise ValueError("invalid namespace prefix %r" % (prefix,))
1054+
1055+
10321056
def register_namespace(prefix, uri):
10331057
"""Register a namespace prefix.
10341058
@@ -1041,8 +1065,7 @@ def register_namespace(prefix, uri):
10411065
ValueError is raised if prefix is reserved or is invalid.
10421066
10431067
"""
1044-
if re.match(r"ns\d+$", prefix):
1045-
raise ValueError("Prefix format reserved for internal use")
1068+
_check_prefix(prefix, uri)
10461069
for k, v in list(_namespace_map.items()):
10471070
if k == uri or v == prefix:
10481071
del _namespace_map[k]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:func:`xml.etree.ElementTree.register_namespace` now raises
2+
:exc:`ValueError` for an invalid prefix, for the reserved ``xmlns`` prefix,
3+
and for the ``xml`` prefix or the XML namespace bound to each other's wrong
4+
counterpart, instead of accepting them and serializing invalid XML.

0 commit comments

Comments
 (0)