https://github.com/python/cpython/commit/5056ac552a413b394fd189ac2c8b8a519a1c9580
commit: 5056ac552a413b394fd189ac2c8b8a519a1c9580
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-09-03T00:10:23+03:00
summary:

gh-156797: Escape the namespace URI in xml.sax.saxutils.XMLGenerator (GH-156799)

The namespace declarations were the only attribute values written without
quoteattr(), so a URI containing "&", "<" or a quote character produced a
document which cannot be parsed.

files:
A Misc/NEWS.d/next/Library/2026-09-01-16-58-30.gh-issue-156797.Qm4tRs.rst
M Lib/test/test_sax.py
M Lib/xml/sax/saxutils.py

diff --git a/Lib/test/test_sax.py b/Lib/test/test_sax.py
index 29babd7bf6996a..46b29650d9383b 100644
--- a/Lib/test/test_sax.py
+++ b/Lib/test/test_sax.py
@@ -614,6 +614,22 @@ def test_xmlgen_ns(self):
            '<ns1:doc xmlns:ns1="%s"><udoc></udoc></ns1:doc>' %
                                          ns_uri))
 
+    def test_xmlgen_ns_escaped_uri(self):
+        uri = 'http://example.org/?a="1"&b=<2>'
+        result = self.ioclass()
+        gen = XMLGenerator(result)
+
+        gen.startDocument()
+        gen.startPrefixMapping("ns1", uri)
+        gen.startElementNS((uri, "doc"), "ns1:doc", {})
+        gen.endElementNS((uri, "doc"), "ns1:doc")
+        gen.endPrefixMapping("ns1")
+        gen.endDocument()
+
+        self.assertEqual(result.getvalue(), self.xml(
+            """<ns1:doc 
xmlns:ns1='http://example.org/?a="1"&amp;b=&lt;2&gt;'>"""
+            "</ns1:doc>"))
+
     def test_xmlgen_ns_empty(self):
         result = self.ioclass()
         gen = XMLGenerator(result, short_empty_elements=True)
diff --git a/Lib/xml/sax/saxutils.py b/Lib/xml/sax/saxutils.py
index 77ab9ee2faf038..920283c3f3496f 100644
--- a/Lib/xml/sax/saxutils.py
+++ b/Lib/xml/sax/saxutils.py
@@ -187,9 +187,9 @@ def startElementNS(self, name, qname, attrs):
 
         for prefix, uri in self._undeclared_ns_maps:
             if prefix:
-                self._write(' xmlns:%s="%s"' % (prefix, uri))
+                self._write(' xmlns:%s=%s' % (prefix, quoteattr(uri)))
             else:
-                self._write(' xmlns="%s"' % uri)
+                self._write(' xmlns=%s' % quoteattr(uri))
         self._undeclared_ns_maps = []
 
         for (name, value) in attrs.items():
diff --git 
a/Misc/NEWS.d/next/Library/2026-09-01-16-58-30.gh-issue-156797.Qm4tRs.rst 
b/Misc/NEWS.d/next/Library/2026-09-01-16-58-30.gh-issue-156797.Qm4tRs.rst
new file mode 100644
index 00000000000000..7879ac7e71c71d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-01-16-58-30.gh-issue-156797.Qm4tRs.rst
@@ -0,0 +1,3 @@
+Fix :class:`xml.sax.saxutils.XMLGenerator`: the namespace URI is now escaped
+in the namespace declaration.  Previously a URI containing ``&``, ``<`` or a
+quote character produced a document which cannot be parsed.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to