diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index a61fb05bf99d87..8bd7b4ada3a592 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -739,9 +739,14 @@ Functions *prefix* is a namespace prefix. *uri* is a namespace uri. Tags and attributes in this namespace will be serialized with the given prefix, if at all possible. + :exc:`ValueError` is raised if *prefix* is invalid or reserved + (``ns`` followed by digits is reserved for the serializer). .. versionadded:: 3.2 + .. versionchanged:: next + Invalid and reserved prefixes are now rejected. + .. function:: SubElement(parent, tag, /, attrib={}, **extra) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 899947c1f8e0d7..6f06c2c8f6a351 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -2848,6 +2848,36 @@ def test_bug_200709_register_namespace(self): self.assertEqual(ET.tostring(e), b'') + def test_register_namespace_invalid(self): + # gh-157518 + xml_ns = 'http://www.w3.org/XML/1998/namespace' + xmlns_ns = 'http://www.w3.org/2000/xmlns/' + nsmap = ET.register_namespace._namespace_map + saved = dict(nsmap) + for prefix, uri in [ + ('ns0', 'u'), ('ns12', 'u'), + ('xml', 'u'), ('foo', xml_ns), + ('xmlns', 'u'), ('foo', xmlns_ns), + ('a:b', 'u'), ('1', 'u'), ('a b', 'u'), ('a\xa0b', 'u'), + ]: + with self.subTest(prefix=prefix, uri=uri): + with self.assertRaises(ValueError): + ET.register_namespace(prefix, uri) + # the registry is not changed + self.assertEqual(nsmap, saved) + for prefix, uri in [(1, 'u'), (b'a', 'u'), ('a', 1), ('a', None)]: + with self.subTest(prefix=prefix, uri=uri): + with self.assertRaises(TypeError): + ET.register_namespace(prefix, uri) + # the xml prefix can be registered for its namespace + ET.register_namespace('xml', xml_ns) + self.assertEqual(ET.register_namespace._namespace_map[xml_ns], 'xml') + # non-ASCII names are valid + self.addCleanup(ET.register_namespace._namespace_map.pop, 'u', None) + ET.register_namespace('\xe9', 'u') + self.assertEqual(ET.tostring(ET.Element('{u}a'), encoding='unicode'), + '<\xe9:a xmlns:\xe9="u" />') + def test_bug_200709_element_comment(self): # Not sure if this can be fixed, really (since the serializer needs # ET.Comment, not cET.comment). diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 0cd04ead801600..10200671634013 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -98,6 +98,7 @@ import contextlib import weakref +from .. import is_valid_name from . import ElementPath @@ -1029,6 +1030,29 @@ def _serialize_text(write, elem): } +_XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace" +_XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/" + +def _check_prefix(prefix, uri): + # Check a namespace prefix and the namespace URI bound to it + # (see Namespaces in XML 1.0, 3 and 4). The empty prefix is allowed. + if not isinstance(prefix, str) or not isinstance(uri, str): + raise TypeError("namespace prefix and URI must be strings") + if re.match(r"ns\d+$", prefix): + raise ValueError("Prefix format reserved for internal use") + if prefix == "xml": + if uri != _XML_NAMESPACE: + raise ValueError("the 'xml' prefix can only be bound to " + "the XML namespace") + elif uri == _XML_NAMESPACE: + raise ValueError("the XML namespace can only be bound to " + "the 'xml' prefix") + elif prefix == "xmlns" or uri == _XMLNS_NAMESPACE: + raise ValueError("the 'xmlns' prefix cannot be bound") + elif prefix and (not is_valid_name(prefix) or ':' in prefix): + raise ValueError("invalid namespace prefix %r" % (prefix,)) + + def register_namespace(prefix, uri): """Register a namespace prefix. @@ -1041,8 +1065,7 @@ def register_namespace(prefix, uri): ValueError is raised if prefix is reserved or is invalid. """ - if re.match(r"ns\d+$", prefix): - raise ValueError("Prefix format reserved for internal use") + _check_prefix(prefix, uri) for k, v in list(_namespace_map.items()): if k == uri or v == prefix: del _namespace_map[k] diff --git a/Misc/NEWS.d/next/Library/2026-09-14-11-00-00.gh-issue-157518.Rk8mTz.rst b/Misc/NEWS.d/next/Library/2026-09-14-11-00-00.gh-issue-157518.Rk8mTz.rst new file mode 100644 index 00000000000000..6c3512e7fefbac --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-14-11-00-00.gh-issue-157518.Rk8mTz.rst @@ -0,0 +1,4 @@ +:func:`xml.etree.ElementTree.register_namespace` now raises +:exc:`ValueError` for an invalid prefix, for the reserved ``xmlns`` prefix, +and for the ``xml`` prefix or the XML namespace bound to each other's wrong +counterpart, instead of accepting them and serializing invalid XML.