Skip to content

Commit ec79fc9

Browse files
committed
Merge branch 'PHP-8.3'
* PHP-8.3: Fix GH-12870: Creating an xmlns attribute results in a DOMException
2 parents 7684a3d + e658f80 commit ec79fc9

File tree

6 files changed

+242
-17
lines changed

6 files changed

+242
-17
lines changed

ext/dom/document.c

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -884,34 +884,66 @@ PHP_METHOD(DOM_Document, createAttributeNS)
884884
xmlNodePtr nodep = NULL, root;
885885
xmlNsPtr nsptr;
886886
int ret;
887-
size_t uri_len = 0, name_len = 0;
888-
char *uri, *name;
887+
zend_string *name, *uri;
889888
char *localname = NULL, *prefix = NULL;
890889
dom_object *intern;
891890
int errorcode;
892891

893892
id = ZEND_THIS;
894-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!s", &uri, &uri_len, &name, &name_len) == FAILURE) {
893+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S!S", &uri, &name) == FAILURE) {
895894
RETURN_THROWS();
896895
}
897896

898897
DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
899898

899+
if (UNEXPECTED(uri == NULL)) {
900+
uri = zend_empty_string;
901+
}
902+
size_t uri_len = ZSTR_LEN(uri);
903+
900904
root = xmlDocGetRootElement(docp);
901905
if (root != NULL) {
902-
errorcode = dom_check_qname(name, &localname, &prefix, uri_len, name_len);
906+
errorcode = dom_check_qname(ZSTR_VAL(name), &localname, &prefix, uri_len, ZSTR_LEN(name));
907+
/* TODO: switch to early goto-out style error-checking */
903908
if (errorcode == 0) {
904909
if (xmlValidateName((xmlChar *) localname, 0) == 0) {
910+
/* If prefix is "xml" and namespace is not the XML namespace, then throw a "NamespaceError" DOMException. */
911+
if (UNEXPECTED(!zend_string_equals_literal(uri, "http://www.w3.org/XML/1998/namespace") && xmlStrEqual(BAD_CAST prefix, BAD_CAST "xml"))) {
912+
errorcode = NAMESPACE_ERR;
913+
goto error;
914+
}
915+
/* If either qualifiedName or prefix is "xmlns" and namespace is not the XMLNS namespace, then throw a "NamespaceError" DOMException. */
916+
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/"))) {
917+
errorcode = NAMESPACE_ERR;
918+
goto error;
919+
}
920+
/* If namespace is the XMLNS namespace and neither qualifiedName nor prefix is "xmlns", then throw a "NamespaceError" DOMException. */
921+
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"))) {
922+
errorcode = NAMESPACE_ERR;
923+
goto error;
924+
}
925+
905926
nodep = (xmlNodePtr) xmlNewDocProp(docp, (xmlChar *) localname, NULL);
906927
if (UNEXPECTED(nodep == NULL)) {
907928
php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
908929
RETURN_THROWS();
909930
}
910931

911932
if (uri_len > 0) {
912-
nsptr = xmlSearchNsByHref(nodep->doc, root, (xmlChar *) uri);
913-
if (nsptr == NULL || nsptr->prefix == NULL) {
914-
nsptr = dom_get_ns(root, uri, &errorcode, prefix ? prefix : "default");
933+
nsptr = xmlSearchNsByHref(docp, root, BAD_CAST ZSTR_VAL(uri));
934+
935+
if (zend_string_equals_literal(name, "xmlns") || xmlStrEqual(BAD_CAST prefix, BAD_CAST "xml")) {
936+
if (nsptr == NULL) {
937+
nsptr = xmlNewNs(NULL, BAD_CAST ZSTR_VAL(uri), BAD_CAST prefix);
938+
php_libxml_set_old_ns(docp, nsptr);
939+
}
940+
} else {
941+
if (nsptr == NULL || nsptr->prefix == NULL) {
942+
nsptr = dom_get_ns_unchecked(root, ZSTR_VAL(uri), prefix ? prefix : "default");
943+
if (UNEXPECTED(nsptr == NULL)) {
944+
errorcode = NAMESPACE_ERR;
945+
}
946+
}
915947
}
916948
xmlSetNs(nodep, nsptr);
917949
}
@@ -924,6 +956,7 @@ PHP_METHOD(DOM_Document, createAttributeNS)
924956
RETURN_FALSE;
925957
}
926958

959+
error:
927960
xmlFree(localname);
928961
if (prefix != NULL) {
929962
xmlFree(prefix);

ext/dom/php_dom.c

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,7 +1551,9 @@ void php_dom_reconcile_attribute_namespace_after_insertion(xmlAttrPtr attrp)
15511551
if (matching_ns && xmlStrEqual(matching_ns->href, attrp->ns->href)) {
15521552
attrp->ns = matching_ns;
15531553
} else {
1554-
xmlReconciliateNs(nodep->doc, nodep);
1554+
if (attrp->ns->prefix != NULL) {
1555+
xmlReconciliateNs(nodep->doc, nodep);
1556+
}
15551557
}
15561558
}
15571559
}
@@ -1691,23 +1693,30 @@ NAMESPACE_ERR: Raised if
16911693
5. the namespaceURI is "http://www.w3.org/2000/xmlns/" and neither the qualifiedName nor its prefix is "xmlns".
16921694
*/
16931695

1696+
xmlNsPtr dom_get_ns_unchecked(xmlNodePtr nodep, char *uri, char *prefix)
1697+
{
1698+
xmlNsPtr nsptr = xmlNewNs(nodep, (xmlChar *)uri, (xmlChar *)prefix);
1699+
if (UNEXPECTED(nsptr == NULL)) {
1700+
/* Either memory allocation failure, or it's because of a prefix conflict.
1701+
* We'll assume a conflict and try again. If it was a memory allocation failure we'll just fail again, whatever.
1702+
* 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?".
1703+
* This branch will also be taken unlikely anyway as in those cases it'll be for allocation failure. */
1704+
return dom_get_ns_resolve_prefix_conflict(nodep, uri);
1705+
}
1706+
1707+
return nsptr;
1708+
}
1709+
16941710
/* {{{ xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) */
16951711
xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) {
16961712
xmlNsPtr nsptr;
16971713

16981714
if (! ((prefix && !strcmp (prefix, "xml") && strcmp(uri, (char *)XML_XML_NAMESPACE)) ||
16991715
(prefix && !strcmp (prefix, "xmlns") && strcmp(uri, (char *)DOM_XMLNS_NAMESPACE)) ||
17001716
(prefix && !strcmp(uri, (char *)DOM_XMLNS_NAMESPACE) && strcmp (prefix, "xmlns")))) {
1701-
nsptr = xmlNewNs(nodep, (xmlChar *)uri, (xmlChar *)prefix);
1717+
nsptr = dom_get_ns_unchecked(nodep, uri, prefix);
17021718
if (UNEXPECTED(nsptr == NULL)) {
1703-
/* Either memory allocation failure, or it's because of a prefix conflict.
1704-
* We'll assume a conflict and try again. If it was a memory allocation failure we'll just fail again, whatever.
1705-
* 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?".
1706-
* This branch will also be taken unlikely anyway as in those cases it'll be for allocation failure. */
1707-
nsptr = dom_get_ns_resolve_prefix_conflict(nodep, uri);
1708-
if (UNEXPECTED(nsptr == NULL)) {
1709-
goto err;
1710-
}
1719+
goto err;
17111720
}
17121721
} else {
17131722
goto err;

ext/dom/php_dom.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ void php_dom_throw_error_with_message(int error_code, char *error_message, int s
130130
void node_list_unlink(xmlNodePtr node);
131131
int dom_check_qname(char *qname, char **localname, char **prefix, int uri_len, int name_len);
132132
xmlNsPtr dom_get_ns(xmlNodePtr node, char *uri, int *errorcode, char *prefix);
133+
xmlNsPtr dom_get_ns_unchecked(xmlNodePtr nodep, char *uri, char *prefix);
133134
void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep);
134135
void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last);
135136
xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName);

ext/dom/tests/gh12870.inc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
function test(?string $uri, string $qualifiedName) {
4+
$uri_readable = is_null($uri) ? 'NULL' : "\"$uri\"";
5+
echo "--- Testing $uri_readable, \"$qualifiedName\" ---\n";
6+
$d = new DOMDocument();
7+
$d->appendChild($d->createElement('root'));
8+
try {
9+
$attr = $d->createAttributeNS($uri, $qualifiedName);
10+
$d->documentElement->setAttributeNodeNS($attr);
11+
echo "Attr prefix: ";
12+
var_dump($attr->prefix);
13+
echo "Attr namespaceURI: ";
14+
var_dump($attr->namespaceURI);
15+
echo "Attr value: ";
16+
var_dump($attr->value);
17+
echo "root namespaceURI: ";
18+
var_dump($d->documentElement->namespaceURI);
19+
echo "Equality check: ";
20+
$parts = explode(':', $qualifiedName);
21+
var_dump($attr === $d->documentElement->getAttributeNodeNS($uri, $parts[count($parts) - 1]));
22+
echo $d->saveXML(), "\n";
23+
} catch (DOMException $e) {
24+
echo "Exception: ", $e->getMessage(), "\n\n";
25+
}
26+
}

ext/dom/tests/gh12870_a.phpt

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
--TEST--
2+
GH-12870 (Creating an xmlns attribute results in a DOMException) - xmlns variations
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
require __DIR__ . '/gh12870.inc';
9+
10+
echo "=== NORMAL CASES ===\n";
11+
12+
test('http://www.w3.org/2000/xmlns/qx', 'foo:xmlns');
13+
test('http://www.w3.org/2000/xmlns/', 'xmlns');
14+
test('http://www.w3.org/2000/xmlns/', 'xmlns:xmlns');
15+
16+
echo "=== ERROR CASES ===\n";
17+
18+
test('http://www.w3.org/2000/xmlns/', 'bar:xmlns');
19+
test('http://www.w3.org/2000/xmlns/a', 'xmlns');
20+
test('http://www.w3.org/2000/xmlns/a', 'xmlns:bar');
21+
test(null, 'xmlns:bar');
22+
test('', 'xmlns');
23+
test('http://www.w3.org/2000/xmlns/', '');
24+
25+
?>
26+
--EXPECT--
27+
=== NORMAL CASES ===
28+
--- Testing "http://www.w3.org/2000/xmlns/qx", "foo:xmlns" ---
29+
Attr prefix: string(3) "foo"
30+
Attr namespaceURI: string(31) "http://www.w3.org/2000/xmlns/qx"
31+
Attr value: string(0) ""
32+
root namespaceURI: NULL
33+
Equality check: bool(true)
34+
<?xml version="1.0"?>
35+
<root xmlns:foo="http://www.w3.org/2000/xmlns/qx" foo:xmlns=""/>
36+
37+
--- Testing "http://www.w3.org/2000/xmlns/", "xmlns" ---
38+
Attr prefix: string(0) ""
39+
Attr namespaceURI: string(29) "http://www.w3.org/2000/xmlns/"
40+
Attr value: string(0) ""
41+
root namespaceURI: NULL
42+
Equality check: bool(true)
43+
<?xml version="1.0"?>
44+
<root xmlns=""/>
45+
46+
--- Testing "http://www.w3.org/2000/xmlns/", "xmlns:xmlns" ---
47+
Attr prefix: string(5) "xmlns"
48+
Attr namespaceURI: string(29) "http://www.w3.org/2000/xmlns/"
49+
Attr value: string(0) ""
50+
root namespaceURI: NULL
51+
Equality check: bool(true)
52+
<?xml version="1.0"?>
53+
<root xmlns:xmlns="http://www.w3.org/2000/xmlns/" xmlns:xmlns=""/>
54+
55+
=== ERROR CASES ===
56+
--- Testing "http://www.w3.org/2000/xmlns/", "bar:xmlns" ---
57+
Exception: Namespace Error
58+
59+
--- Testing "http://www.w3.org/2000/xmlns/a", "xmlns" ---
60+
Exception: Namespace Error
61+
62+
--- Testing "http://www.w3.org/2000/xmlns/a", "xmlns:bar" ---
63+
Exception: Namespace Error
64+
65+
--- Testing NULL, "xmlns:bar" ---
66+
Exception: Namespace Error
67+
68+
--- Testing "", "xmlns" ---
69+
Exception: Namespace Error
70+
71+
--- Testing "http://www.w3.org/2000/xmlns/", "" ---
72+
Exception: Namespace Error

ext/dom/tests/gh12870_b.phpt

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
--TEST--
2+
GH-12870 (Creating an xmlns attribute results in a DOMException) - xml variations
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
require __DIR__ . '/gh12870.inc';
9+
10+
echo "=== NORMAL CASES ===\n";
11+
12+
test('http://www.w3.org/XML/1998/namespaceqx', 'foo:xml');
13+
test('http://www.w3.org/XML/1998/namespace', 'xml');
14+
test('http://www.w3.org/XML/1998/namespace', 'bar:xml');
15+
test('', 'xml');
16+
test('http://www.w3.org/XML/1998/namespacea', 'xml');
17+
18+
echo "=== ERROR CASES ===\n";
19+
20+
test('http://www.w3.org/XML/1998/namespace', 'xmlns:xml');
21+
test('http://www.w3.org/XML/1998/namespace', '');
22+
test('http://www.w3.org/XML/1998/namespacea', 'xml:foo');
23+
test(NULL, 'xml:foo');
24+
25+
?>
26+
--EXPECT--
27+
=== NORMAL CASES ===
28+
--- Testing "http://www.w3.org/XML/1998/namespaceqx", "foo:xml" ---
29+
Attr prefix: string(3) "foo"
30+
Attr namespaceURI: string(38) "http://www.w3.org/XML/1998/namespaceqx"
31+
Attr value: string(0) ""
32+
root namespaceURI: NULL
33+
Equality check: bool(true)
34+
<?xml version="1.0"?>
35+
<root xmlns:foo="http://www.w3.org/XML/1998/namespaceqx" foo:xml=""/>
36+
37+
--- Testing "http://www.w3.org/XML/1998/namespace", "xml" ---
38+
Attr prefix: string(3) "xml"
39+
Attr namespaceURI: string(36) "http://www.w3.org/XML/1998/namespace"
40+
Attr value: string(0) ""
41+
root namespaceURI: NULL
42+
Equality check: bool(true)
43+
<?xml version="1.0"?>
44+
<root xml:xml=""/>
45+
46+
--- Testing "http://www.w3.org/XML/1998/namespace", "bar:xml" ---
47+
Attr prefix: string(3) "xml"
48+
Attr namespaceURI: string(36) "http://www.w3.org/XML/1998/namespace"
49+
Attr value: string(0) ""
50+
root namespaceURI: NULL
51+
Equality check: bool(true)
52+
<?xml version="1.0"?>
53+
<root xml:xml=""/>
54+
55+
--- Testing "", "xml" ---
56+
Attr prefix: string(0) ""
57+
Attr namespaceURI: NULL
58+
Attr value: string(0) ""
59+
root namespaceURI: NULL
60+
Equality check: bool(false)
61+
<?xml version="1.0"?>
62+
<root xml=""/>
63+
64+
--- Testing "http://www.w3.org/XML/1998/namespacea", "xml" ---
65+
Attr prefix: string(7) "default"
66+
Attr namespaceURI: string(37) "http://www.w3.org/XML/1998/namespacea"
67+
Attr value: string(0) ""
68+
root namespaceURI: NULL
69+
Equality check: bool(true)
70+
<?xml version="1.0"?>
71+
<root xmlns:default="http://www.w3.org/XML/1998/namespacea" default:xml=""/>
72+
73+
=== ERROR CASES ===
74+
--- Testing "http://www.w3.org/XML/1998/namespace", "xmlns:xml" ---
75+
Exception: Namespace Error
76+
77+
--- Testing "http://www.w3.org/XML/1998/namespace", "" ---
78+
Exception: Namespace Error
79+
80+
--- Testing "http://www.w3.org/XML/1998/namespacea", "xml:foo" ---
81+
Exception: Namespace Error
82+
83+
--- Testing NULL, "xml:foo" ---
84+
Exception: Namespace Error

0 commit comments

Comments
 (0)