diff --git a/ext/dom/document.c b/ext/dom/document.c
index 665c0a042d6ed..417cb70be9d85 100644
--- a/ext/dom/document.c
+++ b/ext/dom/document.c
@@ -894,29 +894,61 @@ PHP_METHOD(DOMDocument, createAttributeNS)
xmlNodePtr nodep = NULL, root;
xmlNsPtr nsptr;
int ret;
- size_t uri_len = 0, name_len = 0;
- char *uri, *name;
+ zend_string *name, *uri;
char *localname = NULL, *prefix = NULL;
dom_object *intern;
int errorcode;
id = ZEND_THIS;
- if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!s", &uri, &uri_len, &name, &name_len) == FAILURE) {
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "S!S", &uri, &name) == FAILURE) {
RETURN_THROWS();
}
DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
+ if (UNEXPECTED(uri == NULL)) {
+ uri = zend_empty_string;
+ }
+ size_t uri_len = ZSTR_LEN(uri);
+
root = xmlDocGetRootElement(docp);
if (root != NULL) {
- errorcode = dom_check_qname(name, &localname, &prefix, uri_len, name_len);
+ errorcode = dom_check_qname(ZSTR_VAL(name), &localname, &prefix, uri_len, ZSTR_LEN(name));
+ /* TODO: switch to early goto-out style error-checking */
if (errorcode == 0) {
if (xmlValidateName((xmlChar *) localname, 0) == 0) {
+ /* If prefix is "xml" and namespace is not the XML namespace, then throw a "NamespaceError" DOMException. */
+ if (UNEXPECTED(!zend_string_equals_literal(uri, "http://www.w3.org/XML/1998/namespace") && xmlStrEqual(BAD_CAST prefix, BAD_CAST "xml"))) {
+ errorcode = NAMESPACE_ERR;
+ goto error;
+ }
+ /* If either qualifiedName or prefix is "xmlns" and namespace is not the XMLNS namespace, then throw a "NamespaceError" DOMException. */
+ if (UNEXPECTED((zend_string_equals_literal(name, "xmlns") || xmlStrEqual(BAD_CAST prefix, BAD_CAST "xmlns")) && !zend_string_equals_literal(uri, "http://www.w3.org/2000/xmlns/"))) {
+ errorcode = NAMESPACE_ERR;
+ goto error;
+ }
+ /* If namespace is the XMLNS namespace and neither qualifiedName nor prefix is "xmlns", then throw a "NamespaceError" DOMException. */
+ if (UNEXPECTED(zend_string_equals_literal(uri, "http://www.w3.org/2000/xmlns/") && !zend_string_equals_literal(name, "xmlns") && !xmlStrEqual(BAD_CAST prefix, BAD_CAST "xmlns"))) {
+ errorcode = NAMESPACE_ERR;
+ goto error;
+ }
+
nodep = (xmlNodePtr) xmlNewDocProp(docp, (xmlChar *) localname, NULL);
if (nodep != NULL && uri_len > 0) {
- nsptr = xmlSearchNsByHref(nodep->doc, root, (xmlChar *) uri);
- if (nsptr == NULL || nsptr->prefix == NULL) {
- nsptr = dom_get_ns(root, uri, &errorcode, prefix ? prefix : "default");
+ nsptr = xmlSearchNsByHref(docp, root, BAD_CAST ZSTR_VAL(uri));
+
+ if (zend_string_equals_literal(name, "xmlns") || xmlStrEqual(BAD_CAST prefix, BAD_CAST "xml")) {
+ if (nsptr == NULL) {
+ nsptr = xmlNewNs(NULL, BAD_CAST ZSTR_VAL(uri), BAD_CAST prefix);
+ php_libxml_set_old_ns(docp, nsptr);
+ }
+ } else {
+ if (nsptr == NULL || nsptr->prefix == NULL) {
+ nsptr = dom_get_ns_unchecked(root, ZSTR_VAL(uri), prefix ? prefix : "default");
+ if (UNEXPECTED(nsptr == NULL)) {
+ errorcode = NAMESPACE_ERR;
+ }
+ }
}
xmlSetNs(nodep, nsptr);
}
@@ -929,6 +961,7 @@ PHP_METHOD(DOMDocument, createAttributeNS)
RETURN_FALSE;
}
+error:
xmlFree(localname);
if (prefix != NULL) {
xmlFree(prefix);
diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c
index 468d934588054..f008d58089bc3 100644
--- a/ext/dom/php_dom.c
+++ b/ext/dom/php_dom.c
@@ -1596,6 +1596,20 @@ NAMESPACE_ERR: Raised if
5. the namespaceURI is "http://www.w3.org/2000/xmlns/" and neither the qualifiedName nor its prefix is "xmlns".
*/
+xmlNsPtr dom_get_ns_unchecked(xmlNodePtr nodep, char *uri, char *prefix)
+{
+ xmlNsPtr nsptr = xmlNewNs(nodep, (xmlChar *)uri, (xmlChar *)prefix);
+ if (UNEXPECTED(nsptr == NULL)) {
+ /* Either memory allocation failure, or it's because of a prefix conflict.
+ * We'll assume a conflict and try again. If it was a memory allocation failure we'll just fail again, whatever.
+ * This isn't needed for every caller (such as createElementNS & DOMElement::__construct), but isn't harmful and simplifies the mental model "when do I use which function?".
+ * This branch will also be taken unlikely anyway as in those cases it'll be for allocation failure. */
+ return dom_get_ns_resolve_prefix_conflict(nodep, uri);
+ }
+
+ return nsptr;
+}
+
/* {{{ xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) */
xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) {
xmlNsPtr nsptr;
@@ -1603,16 +1617,9 @@ xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) {
if (! ((prefix && !strcmp (prefix, "xml") && strcmp(uri, (char *)XML_XML_NAMESPACE)) ||
(prefix && !strcmp (prefix, "xmlns") && strcmp(uri, (char *)DOM_XMLNS_NAMESPACE)) ||
(prefix && !strcmp(uri, (char *)DOM_XMLNS_NAMESPACE) && strcmp (prefix, "xmlns")))) {
- nsptr = xmlNewNs(nodep, (xmlChar *)uri, (xmlChar *)prefix);
+ nsptr = dom_get_ns_unchecked(nodep, uri, prefix);
if (UNEXPECTED(nsptr == NULL)) {
- /* Either memory allocation failure, or it's because of a prefix conflict.
- * We'll assume a conflict and try again. If it was a memory allocation failure we'll just fail again, whatever.
- * This isn't needed for every caller (such as createElementNS & DOMElement::__construct), but isn't harmful and simplifies the mental model "when do I use which function?".
- * This branch will also be taken unlikely anyway as in those cases it'll be for allocation failure. */
- nsptr = dom_get_ns_resolve_prefix_conflict(nodep, uri);
- if (UNEXPECTED(nsptr == NULL)) {
- goto err;
- }
+ goto err;
}
} else {
goto err;
diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h
index 46900e8e5fe93..c8d13c220de83 100644
--- a/ext/dom/php_dom.h
+++ b/ext/dom/php_dom.h
@@ -128,6 +128,7 @@ void php_dom_throw_error_with_message(int error_code, char *error_message, int s
void node_list_unlink(xmlNodePtr node);
int dom_check_qname(char *qname, char **localname, char **prefix, int uri_len, int name_len);
xmlNsPtr dom_get_ns(xmlNodePtr node, char *uri, int *errorcode, char *prefix);
+xmlNsPtr dom_get_ns_unchecked(xmlNodePtr nodep, char *uri, char *prefix);
void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep);
void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last);
xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName);
diff --git a/ext/dom/tests/gh12870.inc b/ext/dom/tests/gh12870.inc
new file mode 100644
index 0000000000000..d7cfd3df7097e
--- /dev/null
+++ b/ext/dom/tests/gh12870.inc
@@ -0,0 +1,26 @@
+appendChild($d->createElement('root'));
+ try {
+ $attr = $d->createAttributeNS($uri, $qualifiedName);
+ $d->documentElement->setAttributeNodeNS($attr);
+ echo "Attr prefix: ";
+ var_dump($attr->prefix);
+ echo "Attr namespaceURI: ";
+ var_dump($attr->namespaceURI);
+ echo "Attr value: ";
+ var_dump($attr->value);
+ echo "root namespaceURI: ";
+ var_dump($d->documentElement->namespaceURI);
+ echo "Equality check: ";
+ $parts = explode(':', $qualifiedName);
+ var_dump($attr === $d->documentElement->getAttributeNodeNS($uri, $parts[count($parts) - 1]));
+ echo $d->saveXML(), "\n";
+ } catch (DOMException $e) {
+ echo "Exception: ", $e->getMessage(), "\n\n";
+ }
+}
diff --git a/ext/dom/tests/gh12870_a.phpt b/ext/dom/tests/gh12870_a.phpt
new file mode 100644
index 0000000000000..bd085c18bbe05
--- /dev/null
+++ b/ext/dom/tests/gh12870_a.phpt
@@ -0,0 +1,72 @@
+--TEST--
+GH-12870 (Creating an xmlns attribute results in a DOMException) - xmlns variations
+--EXTENSIONS--
+dom
+--FILE--
+
+--EXPECT--
+=== NORMAL CASES ===
+--- Testing "http://www.w3.org/2000/xmlns/qx", "foo:xmlns" ---
+Attr prefix: string(3) "foo"
+Attr namespaceURI: string(31) "http://www.w3.org/2000/xmlns/qx"
+Attr value: string(0) ""
+root namespaceURI: NULL
+Equality check: bool(true)
+
+
+
+--- Testing "http://www.w3.org/2000/xmlns/", "xmlns" ---
+Attr prefix: string(0) ""
+Attr namespaceURI: string(29) "http://www.w3.org/2000/xmlns/"
+Attr value: string(0) ""
+root namespaceURI: NULL
+Equality check: bool(true)
+
+
+
+--- Testing "http://www.w3.org/2000/xmlns/", "xmlns:xmlns" ---
+Attr prefix: string(5) "xmlns"
+Attr namespaceURI: string(29) "http://www.w3.org/2000/xmlns/"
+Attr value: string(0) ""
+root namespaceURI: NULL
+Equality check: bool(true)
+
+
+
+=== ERROR CASES ===
+--- Testing "http://www.w3.org/2000/xmlns/", "bar:xmlns" ---
+Exception: Namespace Error
+
+--- Testing "http://www.w3.org/2000/xmlns/a", "xmlns" ---
+Exception: Namespace Error
+
+--- Testing "http://www.w3.org/2000/xmlns/a", "xmlns:bar" ---
+Exception: Namespace Error
+
+--- Testing NULL, "xmlns:bar" ---
+Exception: Namespace Error
+
+--- Testing "", "xmlns" ---
+Exception: Namespace Error
+
+--- Testing "http://www.w3.org/2000/xmlns/", "" ---
+Exception: Namespace Error
diff --git a/ext/dom/tests/gh12870_b.phpt b/ext/dom/tests/gh12870_b.phpt
new file mode 100644
index 0000000000000..f70c56a477c96
--- /dev/null
+++ b/ext/dom/tests/gh12870_b.phpt
@@ -0,0 +1,84 @@
+--TEST--
+GH-12870 (Creating an xmlns attribute results in a DOMException) - xml variations
+--EXTENSIONS--
+dom
+--FILE--
+
+--EXPECT--
+=== NORMAL CASES ===
+--- Testing "http://www.w3.org/XML/1998/namespaceqx", "foo:xml" ---
+Attr prefix: string(3) "foo"
+Attr namespaceURI: string(38) "http://www.w3.org/XML/1998/namespaceqx"
+Attr value: string(0) ""
+root namespaceURI: NULL
+Equality check: bool(true)
+
+
+
+--- Testing "http://www.w3.org/XML/1998/namespace", "xml" ---
+Attr prefix: string(3) "xml"
+Attr namespaceURI: string(36) "http://www.w3.org/XML/1998/namespace"
+Attr value: string(0) ""
+root namespaceURI: NULL
+Equality check: bool(true)
+
+
+
+--- Testing "http://www.w3.org/XML/1998/namespace", "bar:xml" ---
+Attr prefix: string(3) "xml"
+Attr namespaceURI: string(36) "http://www.w3.org/XML/1998/namespace"
+Attr value: string(0) ""
+root namespaceURI: NULL
+Equality check: bool(true)
+
+
+
+--- Testing "", "xml" ---
+Attr prefix: string(0) ""
+Attr namespaceURI: NULL
+Attr value: string(0) ""
+root namespaceURI: NULL
+Equality check: bool(false)
+
+
+
+--- Testing "http://www.w3.org/XML/1998/namespacea", "xml" ---
+Attr prefix: string(7) "default"
+Attr namespaceURI: string(37) "http://www.w3.org/XML/1998/namespacea"
+Attr value: string(0) ""
+root namespaceURI: NULL
+Equality check: bool(true)
+
+
+
+=== ERROR CASES ===
+--- Testing "http://www.w3.org/XML/1998/namespace", "xmlns:xml" ---
+Exception: Namespace Error
+
+--- Testing "http://www.w3.org/XML/1998/namespace", "" ---
+Exception: Namespace Error
+
+--- Testing "http://www.w3.org/XML/1998/namespacea", "xml:foo" ---
+Exception: Namespace Error
+
+--- Testing NULL, "xml:foo" ---
+Exception: Namespace Error