Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Doc/library/xml.dom.minidom.rst
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ rules apply:
and produced an invalid document,
but removing an absent attribute raised :exc:`~xml.dom.NotFoundErr`.

.. versionchanged:: next
Namespaces are now validated in the factory methods and when setting
:attr:`~xml.dom.Node.prefix` of an attribute.

The following interfaces have no implementation in :mod:`!xml.dom.minidom`:

* :class:`DOMTimeStamp`
Expand Down
14 changes: 14 additions & 0 deletions Doc/library/xml.dom.rst
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,10 @@ inherits properties from :class:`Node`.
:meth:`~Node.insertBefore` or :meth:`~Node.appendChild`.

Raise :exc:`InvalidCharacterErr` if the name is not a valid XML name.
Raise :exc:`NamespaceErr` if the qualified name is malformed,
if it has a prefix and the namespace URI is empty,
or if the prefix is ``'xml'``
and the namespace URI is not the XML namespace.


.. method:: Document.createTextNode(data)
Expand Down Expand Up @@ -748,6 +752,11 @@ inherits properties from :class:`Node`.
:class:`Element` object to use the newly created attribute instance.

Raise :exc:`InvalidCharacterErr` if the name is not a valid XML name.
Raise :exc:`NamespaceErr` if the qualified name is malformed,
if it has a prefix and the namespace URI is empty,
if the prefix is ``'xml'`` and the namespace URI is not the XML namespace,
or if the name or the prefix is ``'xmlns'``
and the namespace URI is not the XMLNS namespace, or vice versa.


.. method:: Document.getElementById(id)
Expand Down Expand Up @@ -913,6 +922,11 @@ of that class.
Note that a qname is the whole attribute name. This is different than above.

Raise :exc:`InvalidCharacterErr` if the name is not a valid XML name.
Raise :exc:`NamespaceErr` if the qualified name is malformed,
if it has a prefix and the namespace URI is empty,
if the prefix is ``'xml'`` and the namespace URI is not the XML namespace,
or if the name or the prefix is ``'xmlns'``
and the namespace URI is not the XMLNS namespace, or vice versa.


.. _dom-attr-objects:
Expand Down
15 changes: 15 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,15 @@ xml
and :meth:`!Document.createEntityReference`.
(Contributed by Jason Orendorff and Serhiy Storchaka in :gh:`44871`.)

* :mod:`xml.dom.minidom` now validates namespaces in the factory methods
:meth:`~xml.dom.Document.createElementNS`,
:meth:`~xml.dom.Document.createAttributeNS`
and :meth:`~xml.dom.Element.setAttributeNS`.
:exc:`~xml.dom.NamespaceErr` is now raised for a malformed qualified name,
for a prefix with an empty namespace, and for illegal use
of the ``xml`` and ``xmlns`` prefixes.
(Contributed by Serhiy Storchaka in :gh:`156665`.)

* Add :meth:`!GetSpecifiedAttributeCount` method
to the :mod:`XML parser <xml.parsers.expat>` objects.
It tells how many of the reported attributes were given in the start tag
Expand Down Expand Up @@ -881,6 +890,12 @@ that may require changes to your code.
Attributes defaulted in the DTD are no longer omitted when parsing.
(Contributed by Jason Orendorff and Serhiy Storchaka in :gh:`44871`.)

* :mod:`xml.dom.minidom` now raises :exc:`~xml.dom.NamespaceErr`
for a malformed qualified name, for a prefix with an empty namespace,
and for illegal use of the ``xml`` and ``xmlns`` prefixes.
Such operations formerly succeeded and produced an invalid document.
(Contributed by Serhiy Storchaka in :gh:`156665`.)

* On Windows, seeking a pipe now fails instead of silently appearing to
succeed: :func:`os.lseek` and :meth:`~io.IOBase.seek` raise :exc:`OSError`,
and :meth:`~io.IOBase.seekable` returns ``False``. As a consequence,
Expand Down
73 changes: 69 additions & 4 deletions Lib/test/test_minidom.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ def testRemoveAttrNS(self):
dom = Document()
child = dom.appendChild(
dom.createElementNS("http://www.python.org", "python:abc"))
child.setAttributeNS("http://www.w3.org", "xmlns:python",
child.setAttributeNS(xml.dom.XMLNS_NAMESPACE, "xmlns:python",
"http://www.python.org")
child.setAttributeNS("http://www.python.org", "python:abcattr", "foo")
# removing an absent attribute has no effect
Expand Down Expand Up @@ -472,11 +472,13 @@ def testGetAttributeNS(self):
dom = Document()
child = dom.appendChild(
dom.createElementNS("http://www.python.org", "python:abc"))
child.setAttributeNS("http://www.w3.org", "xmlns:python",
child.setAttributeNS(xml.dom.XMLNS_NAMESPACE, "xmlns:python",
"http://www.python.org")
self.assertEqual(child.getAttributeNS("http://www.w3.org", "python"),
self.assertEqual(
child.getAttributeNS(xml.dom.XMLNS_NAMESPACE, "python"),
'http://www.python.org')
self.assertEqual(child.getAttributeNS("http://www.w3.org", "other"),
self.assertEqual(
child.getAttributeNS(xml.dom.XMLNS_NAMESPACE, "other"),
'')
child2 = child.appendChild(dom.createElement('abc'))
self.assertEqual(child2.getAttributeNS("http://www.python.org", "missing"),
Expand Down Expand Up @@ -1786,6 +1788,69 @@ def test_cdata_parsing(self):
dom2 = parseString(dom1.toprettyxml())
self.checkWholeText(dom2.getElementsByTagName('node')[0].firstChild, '</data>')

def testNamespaceErr(self):
doc = parseString("<doc/>")
elem = doc.documentElement
XML_NS = xml.dom.XML_NAMESPACE
XMLNS_NS = xml.dom.XMLNS_NAMESPACE
for namespaceURI, qname in [
(None, "p:e"), # a prefix without a namespace
("", "p:e"),
("http://xml.python.org/ns", "p:p:e"), # malformed
("http://xml.python.org/ns", "p:"),
("http://xml.python.org/ns", "p:1e"),
("http://xml.python.org/ns", "xml:e"), # the xml prefix
]:
with self.subTest(namespaceURI=namespaceURI, qname=qname):
self.assertRaises(xml.dom.NamespaceErr,
doc.createElementNS, namespaceURI, qname)
self.assertRaises(xml.dom.NamespaceErr,
doc.createAttributeNS, namespaceURI, qname)
self.assertRaises(xml.dom.NamespaceErr,
elem.setAttributeNS, namespaceURI, qname, "v")

# the xmlns name and prefix are only allowed in the XMLNS namespace
for namespaceURI, qname in [
("http://xml.python.org/ns", "xmlns"),
("http://xml.python.org/ns", "xmlns:p"),
(None, "xmlns:p"),
(XMLNS_NS, "p:a"), # and it allows nothing else
(XMLNS_NS, "a"),
]:
with self.subTest(namespaceURI=namespaceURI, qname=qname):
self.assertRaises(xml.dom.NamespaceErr,
doc.createAttributeNS, namespaceURI, qname)
self.assertRaises(xml.dom.NamespaceErr,
elem.setAttributeNS, namespaceURI, qname, "v")

# valid combinations
doc.createElementNS(None, "e")
doc.createElementNS("http://xml.python.org/ns", "p:e")
doc.createElementNS(XML_NS, "xml:e")
doc.createAttributeNS(None, "a")
doc.createAttributeNS(XML_NS, "xml:lang")
doc.createAttributeNS(XMLNS_NS, "xmlns")
doc.createAttributeNS(XMLNS_NS, "xmlns:p")
elem.setAttributeNS("http://xml.python.org/ns", "p:a", "v")
doc.unlink()

def testAttrPrefix(self):
doc = parseString("<doc/>")
attr = doc.createAttributeNS("http://xml.python.org/ns", "p:a")
self.assertRaises(xml.dom.InvalidCharacterErr,
setattr, attr, "prefix", "q:r")
self.assertRaises(xml.dom.InvalidCharacterErr,
setattr, attr, "prefix", "1q")
self.assertRaises(xml.dom.NamespaceErr,
setattr, attr, "prefix", "xml")
self.assertRaises(xml.dom.NamespaceErr,
setattr, attr, "prefix", "xmlns")
attr.prefix = "q"
self.assertEqual(attr.name, "q:a")
attr.prefix = None
self.assertEqual(attr.name, "a")
doc.unlink()

def testInvalidCharacterErr(self):
doc = parseString("<doc/>")
impl = getDOMImplementation()
Expand Down
45 changes: 36 additions & 9 deletions Lib/xml/dom/minidom.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
import xml
import xml.dom

from xml.dom import EMPTY_NAMESPACE, EMPTY_PREFIX, XMLNS_NAMESPACE, domreg
from xml.dom import (EMPTY_NAMESPACE, EMPTY_PREFIX, XML_NAMESPACE,
XMLNS_NAMESPACE, domreg)
from xml.dom.minicompat import *
from xml.dom.xmlbuilder import DOMImplementationLS, DocumentLS

Expand Down Expand Up @@ -302,6 +303,35 @@ def _check_name(name):
"%r is not a valid XML name" % (name,))


def _check_prefix(prefix, namespaceURI, attribute=False):
if not xml.is_valid_name(prefix) or ':' in prefix:
raise xml.dom.InvalidCharacterErr(
"%r is not a valid namespace prefix" % (prefix,))
if not namespaceURI:
raise xml.dom.NamespaceErr(
"cannot use the prefix %r with an empty namespace" % (prefix,))
if prefix == "xml" and namespaceURI != XML_NAMESPACE:
raise xml.dom.NamespaceErr(
"illegal use of the 'xml' prefix for the wrong namespace")
if attribute and (prefix == "xmlns") != (namespaceURI == XMLNS_NAMESPACE):
raise xml.dom.NamespaceErr(
"illegal use of the 'xmlns' prefix for the wrong namespace")


def _check_qualified_name(namespaceURI, qualifiedName, attribute=False):
"""Check a namespace URI and a qualified name (see DOM Level 2 Core)."""
_check_name(qualifiedName)
prefix, sep, localName = qualifiedName.partition(':')
if sep:
if not localName or ':' in localName or not xml.is_valid_name(localName):
raise xml.dom.NamespaceErr(
"%r is not a valid qualified name" % (qualifiedName,))
_check_prefix(prefix, namespaceURI, attribute)
elif attribute and (qualifiedName == "xmlns") != (namespaceURI == XMLNS_NAMESPACE):
raise xml.dom.NamespaceErr(
"illegal use of the 'xmlns' attribute for the wrong namespace")


def _is_ancestor(node, other):
"Returns true iff node is an ancestor of other."
other = other.parentNode
Expand Down Expand Up @@ -441,11 +471,8 @@ def _get_prefix(self):
return self._prefix

def _set_prefix(self, prefix):
nsuri = self.namespaceURI
if prefix == "xmlns":
if nsuri and nsuri != XMLNS_NAMESPACE:
raise xml.dom.NamespaceErr(
"illegal use of 'xmlns' prefix for the wrong namespace")
if prefix is not None:
_check_prefix(prefix, self.namespaceURI, True)
self._prefix = prefix
if prefix is None:
newName = self.localName
Expand Down Expand Up @@ -804,10 +831,10 @@ def setAttribute(self, attname, value):
_clear_id_cache(self)

def setAttributeNS(self, namespaceURI, qualifiedName, value):
_check_qualified_name(namespaceURI, qualifiedName, True)
prefix, localname = _nssplit(qualifiedName)
attr = self.getAttributeNodeNS(namespaceURI, localname)
if attr is None:
_check_name(qualifiedName)
attr = Attr(qualifiedName, namespaceURI, localname, prefix)
attr.value = value
attr.ownerDocument = self.ownerDocument
Expand Down Expand Up @@ -1814,14 +1841,14 @@ def createAttribute(self, qName):
return a

def createElementNS(self, namespaceURI, qualifiedName):
_check_name(qualifiedName)
_check_qualified_name(namespaceURI, qualifiedName)
prefix, localName = _nssplit(qualifiedName)
e = Element(qualifiedName, namespaceURI, prefix)
e.ownerDocument = self
return e

def createAttributeNS(self, namespaceURI, qualifiedName):
_check_name(qualifiedName)
_check_qualified_name(namespaceURI, qualifiedName, True)
prefix, localName = _nssplit(qualifiedName)
a = Attr(qualifiedName, namespaceURI, localName, prefix)
a.ownerDocument = self
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
:mod:`xml.dom.minidom` now validates namespaces in
:meth:`~xml.dom.Document.createElementNS`,
:meth:`~xml.dom.Document.createAttributeNS` and
:meth:`~xml.dom.Element.setAttributeNS`, and when setting
:attr:`~xml.dom.Node.prefix` of an attribute.
:exc:`~xml.dom.NamespaceErr` is now raised for a malformed qualified name, for
a prefix with an empty namespace, and for illegal use of the ``xml`` and
``xmlns`` prefixes.
Loading