From e3010a590a0664aa1aae175df9f57ec996bc7410 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 14 Sep 2026 22:33:54 +0300 Subject: [PATCH 1/2] 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. --- Doc/library/xml.etree.elementtree.rst | 6 ++++ Lib/test/test_xml_etree.py | 30 +++++++++++++++++++ Lib/xml/etree/ElementTree.py | 27 +++++++++++++++-- ...-09-14-11-00-00.gh-issue-157518.Rk8mTz.rst | 4 +++ 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-14-11-00-00.gh-issue-157518.Rk8mTz.rst diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index a61fb05bf99d87..f03f84d138e255 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -739,9 +739,15 @@ 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 not a valid prefix, + is reserved (``xmlns`` and ``ns`` followed by digits), + or if *prefix* is ``xml`` and *uri* is not the XML namespace or vice versa. .. 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. From 8fd4fc7c77fbd5703005cc9599140eec54d14d12 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 14 Sep 2026 22:45:01 +0300 Subject: [PATCH 2/2] Simplify the documentation of the restrictions --- Doc/library/xml.etree.elementtree.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index f03f84d138e255..8bd7b4ada3a592 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -739,9 +739,8 @@ 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 not a valid prefix, - is reserved (``xmlns`` and ``ns`` followed by digits), - or if *prefix* is ``xml`` and *uri* is not the XML namespace or vice versa. + :exc:`ValueError` is raised if *prefix* is invalid or reserved + (``ns`` followed by digits is reserved for the serializer). .. versionadded:: 3.2