xref: /php-src/ext/dom/node.c (revision 3626e2d5)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Christian Stocker <chregu@php.net>                          |
14    |          Rob Richards <rrichards@php.net>                            |
15    +----------------------------------------------------------------------+
16 */
17 
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 
22 #include "php.h"
23 #if defined(HAVE_LIBXML) && defined(HAVE_DOM)
24 #include "php_dom.h"
25 #include "namespace_compat.h"
26 #include "internal_helpers.h"
27 #include "dom_properties.h"
28 
29 /*
30 * class DOMNode
31 *
32 * URL: https://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1950641247
33 * Since:
34 */
35 
dom_node_concatenated_name_helper(size_t name_len,const char * name,size_t prefix_len,const char * prefix)36 zend_string *dom_node_concatenated_name_helper(size_t name_len, const char *name, size_t prefix_len, const char *prefix)
37 {
38 	/* prefix_len can't overflow because it would need to occupy the entire address space */
39 	zend_string *str = zend_string_safe_alloc(1, name_len, prefix_len + 1, false);
40 	memcpy(ZSTR_VAL(str), prefix, prefix_len);
41 	ZSTR_VAL(str)[prefix_len] = ':';
42 	memcpy(ZSTR_VAL(str) + prefix_len + 1, name, name_len + 1 /* include \0 */);
43 	return str;
44 }
45 
dom_node_get_node_name_attribute_or_element(const xmlNode * nodep,bool uppercase)46 zend_string *dom_node_get_node_name_attribute_or_element(const xmlNode *nodep, bool uppercase)
47 {
48 	zend_string *ret;
49 	size_t name_len = strlen((const char *) nodep->name);
50 	if (nodep->ns != NULL && nodep->ns->prefix != NULL) {
51 		ret = dom_node_concatenated_name_helper(name_len, (const char *) nodep->name, strlen((const char *) nodep->ns->prefix), (const char *) nodep->ns->prefix);
52 	} else {
53 		ret = zend_string_init((const char *) nodep->name, name_len, false);
54 	}
55 	if (uppercase) {
56 		zend_str_toupper(ZSTR_VAL(ret), ZSTR_LEN(ret));
57 	}
58 	return ret;
59 }
60 
php_dom_is_node_connected(const xmlNode * node)61 bool php_dom_is_node_connected(const xmlNode *node)
62 {
63 	ZEND_ASSERT(node != NULL);
64 	do {
65 		if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) {
66 			return true;
67 		}
68 		node = node->parent;
69 	} while (node != NULL);
70 	return false;
71 }
72 
73 /* {{{ nodeName	string
74 readonly=yes
75 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-F68D095
76 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-nodename
77 Since:
78 */
dom_node_node_name_read(dom_object * obj,zval * retval)79 zend_result dom_node_node_name_read(dom_object *obj, zval *retval)
80 {
81 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
82 
83 	bool uppercase = false;
84 
85 	switch (nodep->type) {
86 		case XML_ELEMENT_NODE:
87 			uppercase = php_dom_follow_spec_intern(obj) && php_dom_ns_is_html_and_document_is_html(nodep);
88 			ZEND_FALLTHROUGH;
89 		case XML_ATTRIBUTE_NODE:
90 			ZVAL_NEW_STR(retval, dom_node_get_node_name_attribute_or_element(nodep, uppercase));
91 			break;
92 		case XML_NAMESPACE_DECL: {
93 			xmlNsPtr ns = nodep->ns;
94 			if (ns != NULL && ns->prefix) {
95 				zend_string *str = dom_node_concatenated_name_helper(strlen((const char *) ns->prefix), (const char *) ns->prefix, strlen("xmlns"), "xmlns");
96 				ZVAL_NEW_STR(retval, str);
97 			} else {
98 				ZVAL_STRING(retval, (const char *) nodep->name);
99 			}
100 			break;
101 		}
102 		case XML_DOCUMENT_TYPE_NODE:
103 		case XML_DTD_NODE:
104 		case XML_PI_NODE:
105 		case XML_ENTITY_DECL:
106 		case XML_ENTITY_REF_NODE:
107 		case XML_NOTATION_NODE:
108 			ZVAL_STRING(retval, (char *) nodep->name);
109 			break;
110 		case XML_CDATA_SECTION_NODE:
111 			ZVAL_STRING(retval, "#cdata-section");
112 			break;
113 		case XML_COMMENT_NODE:
114 			ZVAL_STRING(retval, "#comment");
115 			break;
116 		case XML_HTML_DOCUMENT_NODE:
117 		case XML_DOCUMENT_NODE:
118 			ZVAL_STRING(retval, "#document");
119 			break;
120 		case XML_DOCUMENT_FRAG_NODE:
121 			ZVAL_STRING(retval, "#document-fragment");
122 			break;
123 		case XML_TEXT_NODE:
124 			ZVAL_STRING(retval, "#text");
125 			break;
126 		EMPTY_SWITCH_DEFAULT_CASE();
127 	}
128 
129 	return SUCCESS;
130 }
131 
132 /* }}} */
133 
134 /* {{{ nodeValue	string
135 readonly=no
136 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-F68D080
137 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-nodevalue
138 Since:
139 */
dom_node_node_value_read(dom_object * obj,zval * retval)140 zend_result dom_node_node_value_read(dom_object *obj, zval *retval)
141 {
142 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
143 
144 	/* Access to Element node is implemented as a convenience method */
145 	switch (nodep->type) {
146 		case XML_ELEMENT_NODE: {
147 			if (php_dom_follow_spec_intern(obj)) {
148 				ZVAL_NULL(retval);
149 				break;
150 			}
151 			ZEND_FALLTHROUGH;
152 		}
153 		case XML_ATTRIBUTE_NODE:
154 		case XML_TEXT_NODE:
155 		case XML_COMMENT_NODE:
156 		case XML_CDATA_SECTION_NODE:
157 		case XML_PI_NODE:
158 			php_dom_get_content_into_zval(nodep, retval, true);
159 			break;
160 		case XML_NAMESPACE_DECL: {
161 			char *str = (char *) xmlNodeGetContent(nodep->children);
162 			if (str != NULL) {
163 				ZVAL_STRING(retval, str);
164 				xmlFree(str);
165 			} else {
166 				ZVAL_NULL(retval);
167 			}
168 			break;
169 		}
170 		default:
171 			ZVAL_NULL(retval);
172 			break;
173 	}
174 
175 	return SUCCESS;
176 }
177 
dom_node_node_value_write(dom_object * obj,zval * newval)178 zend_result dom_node_node_value_write(dom_object *obj, zval *newval)
179 {
180 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
181 
182 	/* Cannot fail because the type is either null or a string. */
183 	zend_string *str = zval_get_string(newval);
184 
185 	/* Access to Element node is implemented as a convenience method */
186 	switch (nodep->type) {
187 		case XML_ATTRIBUTE_NODE:
188 			if (php_dom_follow_spec_intern(obj)) {
189 				dom_remove_all_children(nodep);
190 				xmlAddChild(nodep, xmlNewTextLen(BAD_CAST ZSTR_VAL(str), ZSTR_LEN(str)));
191 				break;
192 			}
193 			ZEND_FALLTHROUGH;
194 		case XML_ELEMENT_NODE:
195 			dom_remove_all_children(nodep);
196 			ZEND_FALLTHROUGH;
197 		case XML_TEXT_NODE:
198 		case XML_COMMENT_NODE:
199 		case XML_CDATA_SECTION_NODE:
200 		case XML_PI_NODE:
201 			xmlNodeSetContentLen(nodep, BAD_CAST ZSTR_VAL(str), ZSTR_LEN(str));
202 			break;
203 		default:
204 			break;
205 	}
206 
207 	php_libxml_invalidate_node_list_cache(obj->document);
208 
209 	zend_string_release_ex(str, 0);
210 	return SUCCESS;
211 }
212 
213 /* }}} */
214 
215 /* {{{ nodeType	int
216 readonly=yes
217 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-111237558
218 Since:
219 */
dom_node_node_type_read(dom_object * obj,zval * retval)220 zend_result dom_node_node_type_read(dom_object *obj, zval *retval)
221 {
222 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
223 
224 	/* Specs dictate that they are both type XML_DOCUMENT_TYPE_NODE */
225 	if (nodep->type == XML_DTD_NODE) {
226 		ZVAL_LONG(retval, XML_DOCUMENT_TYPE_NODE);
227 	} else {
228 		ZVAL_LONG(retval, nodep->type);
229 	}
230 
231 	return SUCCESS;
232 }
233 
234 /* }}} */
235 
dom_node_parent_get(dom_object * obj,zval * retval,bool only_element)236 static zend_result dom_node_parent_get(dom_object *obj, zval *retval, bool only_element)
237 {
238 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
239 
240 	xmlNodePtr nodeparent = nodep->parent;
241 	if (!nodeparent || (only_element && nodeparent->type != XML_ELEMENT_NODE)) {
242 		ZVAL_NULL(retval);
243 		return SUCCESS;
244 	}
245 
246 	php_dom_create_object(nodeparent, retval, obj);
247 	return SUCCESS;
248 }
249 
250 /* {{{ parentNode	?DomNode
251 readonly=yes
252 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1060184317
253 Since:
254 */
dom_node_parent_node_read(dom_object * obj,zval * retval)255 zend_result dom_node_parent_node_read(dom_object *obj, zval *retval)
256 {
257 	return dom_node_parent_get(obj, retval, false);
258 }
259 
260 /* }}} */
261 
262 /* {{{ parentElement	?DomElement
263 readonly=yes
264 URL: https://dom.spec.whatwg.org/#parent-element
265 Since:
266 */
dom_node_parent_element_read(dom_object * obj,zval * retval)267 zend_result dom_node_parent_element_read(dom_object *obj, zval *retval)
268 {
269 	return dom_node_parent_get(obj, retval, true);
270 }
271 
272 /* }}} */
273 
274 /* {{{ childNodes	DomNodeList
275 readonly=yes
276 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1451460987
277 Since:
278 */
dom_node_child_nodes_read(dom_object * obj,zval * retval)279 zend_result dom_node_child_nodes_read(dom_object *obj, zval *retval)
280 {
281 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
282 
283 	php_dom_create_iterator(retval, DOM_NODELIST, php_dom_follow_spec_intern(obj));
284 	dom_object *intern = Z_DOMOBJ_P(retval);
285 	dom_namednode_iter(obj, XML_ELEMENT_NODE, intern, NULL, NULL, 0, NULL, 0);
286 
287 	return SUCCESS;
288 }
289 /* }}} */
290 
291 /* {{{ firstChild DomNode
292 readonly=yes
293 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-169727388
294 Since:
295 */
dom_node_first_child_read(dom_object * obj,zval * retval)296 zend_result dom_node_first_child_read(dom_object *obj, zval *retval)
297 {
298 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
299 
300 	xmlNodePtr first = NULL;
301 	if (dom_node_children_valid(nodep)) {
302 		first = nodep->children;
303 	}
304 
305 	if (!first) {
306 		ZVAL_NULL(retval);
307 		return SUCCESS;
308 	}
309 
310 	php_dom_create_object(first, retval, obj);
311 	return SUCCESS;
312 }
313 
314 /* }}} */
315 
316 /* {{{ lastChild	DomNode
317 readonly=yes
318 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-61AD09FB
319 Since:
320 */
dom_node_last_child_read(dom_object * obj,zval * retval)321 zend_result dom_node_last_child_read(dom_object *obj, zval *retval)
322 {
323 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
324 
325 	xmlNodePtr last = NULL;
326 	if (dom_node_children_valid(nodep)) {
327 		last = nodep->last;
328 	}
329 
330 	if (!last) {
331 		ZVAL_NULL(retval);
332 		return SUCCESS;
333 	}
334 
335 	php_dom_create_object(last, retval, obj);
336 	return SUCCESS;
337 }
338 
339 /* }}} */
340 
341 /* {{{ previousSibling	DomNode
342 readonly=yes
343 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-640FB3C8
344 Since:
345 */
dom_node_previous_sibling_read(dom_object * obj,zval * retval)346 zend_result dom_node_previous_sibling_read(dom_object *obj, zval *retval)
347 {
348 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
349 
350 	xmlNodePtr prevsib = nodep->prev;
351 	if (!prevsib) {
352 		ZVAL_NULL(retval);
353 		return SUCCESS;
354 	}
355 
356 	php_dom_create_object(prevsib, retval, obj);
357 	return SUCCESS;
358 }
359 
360 /* }}} */
361 
362 /* {{{ nextSibling	DomNode
363 readonly=yes
364 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-6AC54C2F
365 Since:
366 */
dom_node_next_sibling_read(dom_object * obj,zval * retval)367 zend_result dom_node_next_sibling_read(dom_object *obj, zval *retval)
368 {
369 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
370 
371 	xmlNodePtr nextsib = nodep->next;
372 	if (!nextsib) {
373 		ZVAL_NULL(retval);
374 		return SUCCESS;
375 	}
376 
377 	php_dom_create_object(nextsib, retval, obj);
378 	return SUCCESS;
379 }
380 
381 /* }}} */
382 
383 /* {{{ previousElementSibling	DomNode
384 readonly=yes
385 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-640FB3C8
386 Since:
387 */
dom_node_previous_element_sibling_read(dom_object * obj,zval * retval)388 zend_result dom_node_previous_element_sibling_read(dom_object *obj, zval *retval)
389 {
390 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
391 
392 	xmlNodePtr prevsib = nodep->prev;
393 
394 	while (prevsib && prevsib->type != XML_ELEMENT_NODE) {
395 		prevsib = prevsib->prev;
396 	}
397 
398 	if (!prevsib) {
399 		ZVAL_NULL(retval);
400 		return SUCCESS;
401 	}
402 
403 	php_dom_create_object(prevsib, retval, obj);
404 	return SUCCESS;
405 }
406 
407 /* }}} */
408 
409 /* {{{ nextElementSibling	DomNode
410 readonly=yes
411 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-6AC54C2F
412 Since:
413 */
dom_node_next_element_sibling_read(dom_object * obj,zval * retval)414 zend_result dom_node_next_element_sibling_read(dom_object *obj, zval *retval)
415 {
416 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
417 
418 	xmlNodePtr nextsib = nodep->next;
419 
420 	while (nextsib != NULL && nextsib->type != XML_ELEMENT_NODE) {
421 		nextsib = nextsib->next;
422 	}
423 
424 	if (!nextsib) {
425 		ZVAL_NULL(retval);
426 		return SUCCESS;
427 	}
428 
429 	php_dom_create_object(nextsib, retval, obj);
430 	return SUCCESS;
431 }
432 
433 /* }}} */
434 
435 /* {{{ attributes	DomNamedNodeMap
436 readonly=yes
437 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-84CF096
438 Since:
439 */
dom_node_attributes_read(dom_object * obj,zval * retval)440 zend_result dom_node_attributes_read(dom_object *obj, zval *retval)
441 {
442 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
443 
444 	if (nodep->type == XML_ELEMENT_NODE) {
445 		php_dom_create_iterator(retval, DOM_NAMEDNODEMAP, php_dom_follow_spec_intern(obj));
446 		dom_object *intern = Z_DOMOBJ_P(retval);
447 		dom_namednode_iter(obj, XML_ATTRIBUTE_NODE, intern, NULL, NULL, 0, NULL, 0);
448 	} else {
449 		ZVAL_NULL(retval);
450 	}
451 
452 	return SUCCESS;
453 }
454 
455 /* }}} */
456 
457 /* {{{ isConnected	boolean
458 readonly=yes
459 URL: https://dom.spec.whatwg.org/#dom-node-isconnected
460 Since:
461 */
dom_node_is_connected_read(dom_object * obj,zval * retval)462 zend_result dom_node_is_connected_read(dom_object *obj, zval *retval)
463 {
464 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
465 	ZVAL_BOOL(retval, php_dom_is_node_connected(nodep));
466 	return SUCCESS;
467 }
468 /* }}} */
469 
470 /* {{{ ownerDocument	DomDocument
471 readonly=yes
472 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-node-ownerDoc
473 Since:
474 */
dom_node_owner_document_read(dom_object * obj,zval * retval)475 zend_result dom_node_owner_document_read(dom_object *obj, zval *retval)
476 {
477 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
478 
479 	if (nodep->type == XML_DOCUMENT_NODE || nodep->type == XML_HTML_DOCUMENT_NODE) {
480 		ZVAL_NULL(retval);
481 		return SUCCESS;
482 	}
483 
484 	xmlDocPtr docp = nodep->doc;
485 	if (!docp) {
486 		return FAILURE;
487 	}
488 
489 	php_dom_create_object((xmlNodePtr) docp, retval, obj);
490 	return SUCCESS;
491 }
492 
493 /* }}} */
494 
495 /* {{{ namespaceUri	string
496 readonly=yes
497 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-NodeNSname
498 Since: DOM Level 2
499 */
dom_node_namespace_uri_read(dom_object * obj,zval * retval)500 zend_result dom_node_namespace_uri_read(dom_object *obj, zval *retval)
501 {
502 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
503 
504 	const char *str = NULL;
505 	switch (nodep->type) {
506 		case XML_ELEMENT_NODE:
507 		case XML_ATTRIBUTE_NODE:
508 		case XML_NAMESPACE_DECL:
509 			if (nodep->ns != NULL) {
510 				str = (const char *) nodep->ns->href;
511 			}
512 			break;
513 		default:
514 			str = NULL;
515 			break;
516 	}
517 
518 	if (str != NULL) {
519 		ZVAL_STRING(retval, str);
520 	} else {
521 		ZVAL_NULL(retval);
522 	}
523 
524 	return SUCCESS;
525 }
526 
527 /* }}} */
528 
529 /* {{{ prefix	string
530 readonly=no
531 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-NodeNSPrefix
532 Modern spec URL: https://dom.spec.whatwg.org/#concept-element-namespace-prefix
533 Since: DOM Level 2
534 */
dom_node_prefix_read(dom_object * obj,zval * retval)535 zend_result dom_node_prefix_read(dom_object *obj, zval *retval)
536 {
537 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
538 
539 	const char *str = NULL;
540 	switch (nodep->type) {
541 		case XML_ELEMENT_NODE:
542 		case XML_ATTRIBUTE_NODE:
543 		case XML_NAMESPACE_DECL: {
544 			xmlNsPtr ns = nodep->ns;
545 			if (ns != NULL && ns->prefix) {
546 				str = (char *) ns->prefix;
547 			}
548 			break;
549 		}
550 		default:
551 			str = NULL;
552 			break;
553 	}
554 
555 	if (str == NULL) {
556 		ZVAL_EMPTY_STRING(retval);
557 	} else {
558 		ZVAL_STRING(retval, str);
559 	}
560 	return SUCCESS;
561 }
562 
dom_modern_node_prefix_read(dom_object * obj,zval * retval)563 zend_result dom_modern_node_prefix_read(dom_object *obj, zval *retval)
564 {
565 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
566 
567 	xmlNsPtr ns = nodep->ns;
568 	if (ns != NULL && ns->prefix != NULL) {
569 		ZVAL_STRING(retval, (const char *) ns->prefix);
570 	} else {
571 		ZVAL_NULL(retval);
572 	}
573 	return SUCCESS;
574 }
575 
dom_node_prefix_write(dom_object * obj,zval * newval)576 zend_result dom_node_prefix_write(dom_object *obj, zval *newval)
577 {
578 	zend_string *prefix_str;
579 	xmlNode *nsnode = NULL;
580 	xmlNsPtr ns = NULL, curns;
581 	char *strURI;
582 	char *prefix;
583 
584 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
585 
586 	switch (nodep->type) {
587 		case XML_ELEMENT_NODE:
588 			nsnode = nodep;
589 			ZEND_FALLTHROUGH;
590 		case XML_ATTRIBUTE_NODE:
591 			if (nsnode == NULL) {
592 				nsnode = nodep->parent;
593 				if (nsnode == NULL) {
594 					nsnode = xmlDocGetRootElement(nodep->doc);
595 				}
596 			}
597 			/* Typed property, this is already a string */
598 			ZEND_ASSERT(Z_TYPE_P(newval) == IS_STRING);
599 			prefix_str = Z_STR_P(newval);
600 
601 			prefix = ZSTR_VAL(prefix_str);
602 			if (*prefix == '\0') {
603 				/* The empty string namespace prefix does not exist.
604 				 * We should fall back to the default namespace in this case. */
605 				prefix = NULL;
606 			}
607 			if (nsnode && nodep->ns != NULL && !xmlStrEqual(nodep->ns->prefix, BAD_CAST prefix)) {
608 				strURI = (char *) nodep->ns->href;
609 				/* Validate namespace naming constraints */
610 				if (strURI == NULL ||
611 					(zend_string_equals_literal(prefix_str, "xml") && strcmp(strURI, (char *) XML_XML_NAMESPACE)) ||
612 					(nodep->type == XML_ATTRIBUTE_NODE && zend_string_equals_literal(prefix_str, "xmlns") &&
613 					 strcmp(strURI, DOM_XMLNS_NS_URI)) ||
614 					(nodep->type == XML_ATTRIBUTE_NODE && !strcmp((char *) nodep->name, "xmlns"))) {
615 					php_dom_throw_error(NAMESPACE_ERR, dom_get_strict_error(obj->document));
616 					return FAILURE;
617 				} else {
618 					curns = nsnode->nsDef;
619 					while (curns != NULL) {
620 						if (xmlStrEqual(BAD_CAST prefix, curns->prefix) && xmlStrEqual(nodep->ns->href, curns->href)) {
621 							ns = curns;
622 							break;
623 						}
624 						curns = curns->next;
625 					}
626 					if (ns == NULL) {
627 						ns = xmlNewNs(nsnode, nodep->ns->href, BAD_CAST prefix);
628 						/* Sadly, we cannot distinguish between OOM and namespace conflict.
629 						 * But OOM will almost never happen. */
630 						if (UNEXPECTED(ns == NULL)) {
631 							php_dom_throw_error(NAMESPACE_ERR, /* strict */ true);
632 							return FAILURE;
633 						}
634 					}
635 				}
636 
637 				xmlSetNs(nodep, ns);
638 			}
639 			break;
640 		default:
641 			break;
642 	}
643 
644 	return SUCCESS;
645 }
646 
647 /* }}} */
648 
649 /* {{{ localName	string
650 readonly=yes
651 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-NodeNSLocalN
652 Since: DOM Level 2
653 */
dom_node_local_name_read(dom_object * obj,zval * retval)654 zend_result dom_node_local_name_read(dom_object *obj, zval *retval)
655 {
656 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
657 
658 	if (nodep->type == XML_ELEMENT_NODE || nodep->type == XML_ATTRIBUTE_NODE || nodep->type == XML_NAMESPACE_DECL) {
659 		ZVAL_STRING(retval, (char *) (nodep->name));
660 	} else {
661 		ZVAL_NULL(retval);
662 	}
663 
664 	return SUCCESS;
665 }
666 
667 /* }}} */
668 
669 /* {{{ baseURI	string
670 readonly=yes
671 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Node3-baseURI
672 Since: DOM Level 3
673 */
dom_node_base_uri_read(dom_object * obj,zval * retval)674 zend_result dom_node_base_uri_read(dom_object *obj, zval *retval)
675 {
676 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
677 
678 	xmlChar *baseuri = xmlNodeGetBase(nodep->doc, nodep);
679 	if (baseuri) {
680 		ZVAL_STRING(retval, (const char *) baseuri);
681 		xmlFree(baseuri);
682 	} else {
683 		if (php_dom_follow_spec_intern(obj)) {
684 			if (nodep->doc->URL) {
685 				ZVAL_STRING(retval, (const char *) nodep->doc->URL);
686 			} else {
687 				ZVAL_STRING(retval, "about:blank");
688 			}
689 		} else {
690 			ZVAL_NULL(retval);
691 		}
692 	}
693 
694 	return SUCCESS;
695 }
696 
697 /* }}} */
698 
699 /* {{{ textContent	string
700 readonly=no
701 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Node3-textContent
702 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-textcontent
703 Since: DOM Level 3
704 */
705 /* Determines when the operation is a no-op. */
dom_skip_text_content(dom_object * obj,xmlNodePtr nodep)706 static bool dom_skip_text_content(dom_object *obj, xmlNodePtr nodep)
707 {
708 	if (php_dom_follow_spec_intern(obj)) {
709 		int type = nodep->type;
710 		if (type != XML_DOCUMENT_FRAG_NODE && type != XML_ELEMENT_NODE && type != XML_ATTRIBUTE_NODE
711 			&& type != XML_TEXT_NODE && type != XML_CDATA_SECTION_NODE && type != XML_COMMENT_NODE && type != XML_PI_NODE) {
712 			/* Yes, success... It's a no-op for these cases. */
713 			return true;
714 		}
715 	}
716 	return false;
717 }
718 
dom_node_text_content_read(dom_object * obj,zval * retval)719 zend_result dom_node_text_content_read(dom_object *obj, zval *retval)
720 {
721 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
722 
723 	if (dom_skip_text_content(obj, nodep)) {
724 		ZVAL_NULL(retval);
725 	} else {
726 		php_dom_get_content_into_zval(nodep, retval, false);
727 	}
728 
729 	return SUCCESS;
730 }
731 
dom_node_text_content_write(dom_object * obj,zval * newval)732 zend_result dom_node_text_content_write(dom_object *obj, zval *newval)
733 {
734 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
735 
736 	php_libxml_invalidate_node_list_cache(obj->document);
737 
738 	/* Typed property, this is already a string */
739 	ZEND_ASSERT(Z_TYPE_P(newval) == IS_STRING || Z_TYPE_P(newval) == IS_NULL);
740 	const xmlChar *xmlChars;
741 	size_t len;
742 	if (Z_TYPE_P(newval) == IS_NULL) {
743 		xmlChars = (const xmlChar *) "";
744 		len = 0;
745 	} else {
746 		xmlChars = (const xmlChar *) Z_STRVAL_P(newval);
747 		len = Z_STRLEN_P(newval);
748 	}
749 
750 	int type = nodep->type;
751 
752 	/* We can't directly call xmlNodeSetContent, because it might encode the string through
753 	 * xmlStringLenGetNodeList for types XML_DOCUMENT_FRAG_NODE, XML_ELEMENT_NODE, XML_ATTRIBUTE_NODE.
754 	 * See tree.c:xmlNodeSetContent in libxml.
755 	 * In these cases we need to use a text node to avoid the encoding.
756 	 * For the other cases, we *can* rely on xmlNodeSetContent because it is either a no-op, or handles
757 	 * the content without encoding. */
758 	if (type == XML_DOCUMENT_FRAG_NODE || type == XML_ELEMENT_NODE || type == XML_ATTRIBUTE_NODE) {
759 		dom_remove_all_children(nodep);
760 		xmlNode *textNode = xmlNewDocTextLen(nodep->doc, xmlChars, len);
761 		xmlAddChild(nodep, textNode);
762 	} else {
763 		xmlNodeSetContent(nodep, xmlChars);
764 	}
765 
766 	return SUCCESS;
767 }
768 
769 /* }}} */
770 
_php_dom_insert_fragment(xmlNodePtr nodep,xmlNodePtr prevsib,xmlNodePtr nextsib,xmlNodePtr fragment,dom_object * intern)771 static xmlNodePtr _php_dom_insert_fragment(xmlNodePtr nodep, xmlNodePtr prevsib, xmlNodePtr nextsib, xmlNodePtr fragment, dom_object *intern) /* {{{ */
772 {
773 	xmlNodePtr newchild, node;
774 
775 	newchild = fragment->children;
776 
777 	if (newchild) {
778 		if (prevsib == NULL) {
779 			nodep->children = newchild;
780 		} else {
781 			prevsib->next = newchild;
782 		}
783 		newchild->prev = prevsib;
784 		if (nextsib == NULL) {
785 			nodep->last = fragment->last;
786 		} else {
787 			fragment->last->next = nextsib;
788 			nextsib->prev = fragment->last;
789 		}
790 
791 		node = newchild;
792 		while (node != NULL) {
793 			node->parent = nodep;
794 			if (node->doc != nodep->doc) {
795 				xmlSetTreeDoc(node, nodep->doc);
796 				dom_object *childobj = node->_private;
797 				if (childobj != NULL) {
798 					childobj->document = intern->document;
799 					php_libxml_increment_doc_ref((php_libxml_node_object *)childobj, NULL);
800 				}
801 			}
802 			if (node == fragment->last) {
803 				break;
804 			}
805 			node = node->next;
806 		}
807 
808 		fragment->children = NULL;
809 		fragment->last = NULL;
810 	}
811 
812 	return newchild;
813 }
814 /* }}} */
815 
816 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-952280727
817 Since:
818 */
dom_node_insert_before_legacy(zval * return_value,zval * ref,dom_object * intern,dom_object * childobj,xmlNodePtr parentp,xmlNodePtr child)819 static void dom_node_insert_before_legacy(zval *return_value, zval *ref, dom_object *intern, dom_object *childobj, xmlNodePtr parentp, xmlNodePtr child)
820 {
821 	if (!dom_node_children_valid(parentp)) {
822 		RETURN_FALSE;
823 	}
824 
825 	xmlNodePtr new_child = NULL;
826 	bool stricterror = dom_get_strict_error(intern->document);
827 
828 	if (dom_node_is_read_only(parentp) == SUCCESS ||
829 		(child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
830 		php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
831 		RETURN_FALSE;
832 	}
833 
834 	if (dom_hierarchy(parentp, child) == FAILURE) {
835 		php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
836 		RETURN_FALSE;
837 	}
838 
839 	if (child->doc != parentp->doc && child->doc != NULL) {
840 		php_dom_throw_error(WRONG_DOCUMENT_ERR, stricterror);
841 		RETURN_FALSE;
842 	}
843 
844 	if (child->type == XML_DOCUMENT_FRAG_NODE && child->children == NULL) {
845 		/* TODO Drop Warning? */
846 		php_error_docref(NULL, E_WARNING, "Document Fragment is empty");
847 		RETURN_FALSE;
848 	}
849 
850 	if (child->doc == NULL && parentp->doc != NULL) {
851 		childobj->document = intern->document;
852 		php_libxml_increment_doc_ref((php_libxml_node_object *)childobj, NULL);
853 	}
854 
855 	php_libxml_invalidate_node_list_cache(intern->document);
856 
857 	if (ref != NULL) {
858 		xmlNodePtr refp;
859 		dom_object *refpobj;
860 		DOM_GET_OBJ(refp, ref, xmlNodePtr, refpobj);
861 		if (refp->parent != parentp) {
862 			php_dom_throw_error(NOT_FOUND_ERR, stricterror);
863 			RETURN_FALSE;
864 		}
865 
866 		if (child->parent != NULL) {
867 			xmlUnlinkNode(child);
868 		}
869 
870 		if (child->type == XML_TEXT_NODE && (refp->type == XML_TEXT_NODE ||
871 			(refp->prev != NULL && refp->prev->type == XML_TEXT_NODE))) {
872 			if (child->doc == NULL) {
873 				xmlSetTreeDoc(child, parentp->doc);
874 			}
875 			new_child = child;
876 			new_child->parent = refp->parent;
877 			new_child->next = refp;
878 			new_child->prev = refp->prev;
879 			refp->prev = new_child;
880 			if (new_child->prev != NULL) {
881 				new_child->prev->next = new_child;
882 			}
883 			if (new_child->parent != NULL) {
884 				if (new_child->parent->children == refp) {
885 					new_child->parent->children = new_child;
886 				}
887 			}
888 
889 		} else if (child->type == XML_ATTRIBUTE_NODE) {
890 			xmlAttrPtr lastattr;
891 
892 			if (child->ns == NULL)
893 				lastattr = xmlHasProp(refp->parent, child->name);
894 			else
895 				lastattr = xmlHasNsProp(refp->parent, child->name, child->ns->href);
896 			if (lastattr != NULL && lastattr->type != XML_ATTRIBUTE_DECL) {
897 				if (lastattr != (xmlAttrPtr) child) {
898 					xmlUnlinkNode((xmlNodePtr) lastattr);
899 					php_libxml_node_free_resource((xmlNodePtr) lastattr);
900 				} else {
901 					DOM_RET_OBJ(child, intern);
902 					return;
903 				}
904 			}
905 			new_child = xmlAddPrevSibling(refp, child);
906 			if (UNEXPECTED(NULL == new_child)) {
907 				goto cannot_add;
908 			}
909 		} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
910 			xmlNodePtr last = child->last;
911 			new_child = _php_dom_insert_fragment(parentp, refp->prev, refp, child, intern);
912 			dom_reconcile_ns_list(parentp->doc, new_child, last);
913 		} else {
914 			new_child = xmlAddPrevSibling(refp, child);
915 			if (UNEXPECTED(NULL == new_child)) {
916 				goto cannot_add;
917 			}
918 			dom_reconcile_ns(parentp->doc, new_child);
919 		}
920 	} else {
921 		if (child->parent != NULL){
922 			xmlUnlinkNode(child);
923 		}
924 		if (child->type == XML_TEXT_NODE && parentp->last != NULL && parentp->last->type == XML_TEXT_NODE) {
925 			child->parent = parentp;
926 			if (child->doc == NULL) {
927 				xmlSetTreeDoc(child, parentp->doc);
928 			}
929 			new_child = child;
930 			if (parentp->children == NULL) {
931 				parentp->children = child;
932 				parentp->last = child;
933 			} else {
934 				child = parentp->last;
935 				child->next = new_child;
936 				new_child->prev = child;
937 				parentp->last = new_child;
938 			}
939 		} else 	if (child->type == XML_ATTRIBUTE_NODE) {
940 			xmlAttrPtr lastattr;
941 
942 			if (child->ns == NULL)
943 				lastattr = xmlHasProp(parentp, child->name);
944 			else
945 				lastattr = xmlHasNsProp(parentp, child->name, child->ns->href);
946 			if (lastattr != NULL && lastattr->type != XML_ATTRIBUTE_DECL) {
947 				if (lastattr != (xmlAttrPtr) child) {
948 					xmlUnlinkNode((xmlNodePtr) lastattr);
949 					php_libxml_node_free_resource((xmlNodePtr) lastattr);
950 				} else {
951 					DOM_RET_OBJ(child, intern);
952 					return;
953 				}
954 			}
955 			new_child = xmlAddChild(parentp, child);
956 			if (UNEXPECTED(NULL == new_child)) {
957 				goto cannot_add;
958 			}
959 		} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
960 			xmlNodePtr last = child->last;
961 			new_child = _php_dom_insert_fragment(parentp, parentp->last, NULL, child, intern);
962 			dom_reconcile_ns_list(parentp->doc, new_child, last);
963 		} else {
964 			new_child = xmlAddChild(parentp, child);
965 			if (UNEXPECTED(NULL == new_child)) {
966 				goto cannot_add;
967 			}
968 			dom_reconcile_ns(parentp->doc, new_child);
969 		}
970 	}
971 
972 	DOM_RET_OBJ(new_child, intern);
973 	return;
974 cannot_add:
975 	zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode");
976 	RETURN_THROWS();
977 }
978 /* }}} end dom_node_insert_before */
979 
980 /* https://dom.spec.whatwg.org/#dom-node-insertbefore */
dom_node_insert_before_modern(zval * return_value,zval * ref,dom_object * intern,dom_object * childobj,xmlNodePtr parentp,xmlNodePtr child)981 static void dom_node_insert_before_modern(zval *return_value, zval *ref, dom_object *intern, dom_object *childobj, xmlNodePtr parentp, xmlNodePtr child)
982 {
983 	xmlNodePtr refp = NULL;
984 	dom_object *refobjp;
985 	if (php_dom_pre_insert_is_parent_invalid(parentp)) {
986 		php_dom_throw_error(HIERARCHY_REQUEST_ERR, /* strict */ true);
987 		RETURN_THROWS();
988 	}
989 	if (ref != NULL) {
990 		DOM_GET_OBJ(refp, ref, xmlNodePtr, refobjp);
991 	}
992 	php_libxml_invalidate_node_list_cache(intern->document);
993 	php_dom_pre_insert(intern->document, child, parentp, refp);
994 	DOM_RET_OBJ(child, intern);
995 }
996 
dom_node_insert_before(INTERNAL_FUNCTION_PARAMETERS,bool modern)997 static void dom_node_insert_before(INTERNAL_FUNCTION_PARAMETERS, bool modern)
998 {
999 	zval *id, *node, *ref = NULL;
1000 	xmlNodePtr child, parentp;
1001 	dom_object *intern, *childobj;
1002 
1003 	id = ZEND_THIS;
1004 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|O!", &node, dom_get_node_ce(modern), &ref, dom_get_node_ce(modern)) == FAILURE) {
1005 		RETURN_THROWS();
1006 	}
1007 
1008 	DOM_GET_OBJ(parentp, id, xmlNodePtr, intern);
1009 
1010 	DOM_GET_OBJ(child, node, xmlNodePtr, childobj);
1011 
1012 	if (modern) {
1013 		dom_node_insert_before_modern(return_value, ref, intern, childobj, parentp, child);
1014 	} else {
1015 		dom_node_insert_before_legacy(return_value, ref, intern, childobj, parentp, child);
1016 	}
1017 }
1018 
PHP_METHOD(DOMNode,insertBefore)1019 PHP_METHOD(DOMNode, insertBefore)
1020 {
1021 	dom_node_insert_before(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1022 }
1023 
PHP_METHOD(DOM_Node,insertBefore)1024 PHP_METHOD(DOM_Node, insertBefore)
1025 {
1026 	dom_node_insert_before(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1027 }
1028 
1029 /* https://dom.spec.whatwg.org/#concept-node-replace */
dom_replace_node_validity_checks(xmlNodePtr parent,xmlNodePtr node,xmlNodePtr child)1030 static zend_result dom_replace_node_validity_checks(xmlNodePtr parent, xmlNodePtr node, xmlNodePtr child)
1031 {
1032 	/* 1. If parent is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException. */
1033 	if (php_dom_pre_insert_is_parent_invalid(parent)) {
1034 		php_dom_throw_error(HIERARCHY_REQUEST_ERR, /* strict */ true);
1035 		return FAILURE;
1036 	}
1037 
1038 	/* 2. If node is a host-including inclusive ancestor of parent, then throw a "HierarchyRequestError" DOMException. */
1039 	if (dom_hierarchy(parent, node) != SUCCESS) {
1040 		php_dom_throw_error(HIERARCHY_REQUEST_ERR, /* strict */ true);
1041 		return FAILURE;
1042 	}
1043 
1044 	/* 3. If child’s parent is not parent, then throw a "NotFoundError" DOMException. */
1045 	if (child->parent != parent) {
1046 		php_dom_throw_error(NOT_FOUND_ERR, /* strict */ true);
1047 		return FAILURE;
1048 	}
1049 
1050 	/* 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException. */
1051 	if (node->type != XML_DOCUMENT_FRAG_NODE
1052 		&& node->type != XML_DTD_NODE
1053 		&& node->type != XML_ELEMENT_NODE
1054 		&& node->type != XML_TEXT_NODE
1055 		&& node->type != XML_CDATA_SECTION_NODE
1056 		&& node->type != XML_COMMENT_NODE
1057 		&& node->type != XML_PI_NODE) {
1058 		php_dom_throw_error(HIERARCHY_REQUEST_ERR, /* strict */ true);
1059 		return FAILURE;
1060 	}
1061 
1062 	/* 5. If either node is a Text node and parent is a document, or node is a doctype and parent is not a document,
1063 	 *    then throw a "HierarchyRequestError" DOMException. */
1064 	bool parent_is_document = parent->type == XML_DOCUMENT_NODE || parent->type == XML_HTML_DOCUMENT_NODE;
1065 	if (parent_is_document && (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE)) {
1066 		php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot insert text as a child of a document", /* strict */ true);
1067 		return FAILURE;
1068 	}
1069 	if (!parent_is_document && node->type == XML_DTD_NODE) {
1070 		php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot insert a document type into anything other than a document", /* strict */ true);
1071 		return FAILURE;
1072 	}
1073 
1074 	/* 6. If parent is a document, and any of the statements below, switched on the interface node implements, are true,
1075 	 *    then throw a "HierarchyRequestError" DOMException.
1076 	 *    Spec note: These statements _slightly_ differ from the pre-insert algorithm. */
1077 	if (parent_is_document) {
1078 		/* DocumentFragment */
1079 		if (node->type == XML_DOCUMENT_FRAG_NODE) {
1080 			if (!php_dom_fragment_insertion_hierarchy_check_replace(parent, node, child)) {
1081 				return FAILURE;
1082 			}
1083 		}
1084 		/* Element */
1085 		else if (node->type == XML_ELEMENT_NODE) {
1086 			/* parent has an element child that is not child ... */
1087 			if (xmlDocGetRootElement((xmlDocPtr) parent) != child) {
1088 				php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot have more than one element child in a document", /* strict */ true);
1089 				return FAILURE;
1090 			}
1091 			/* ... or a doctype is following child. */
1092 			if (php_dom_has_sibling_following_node(child, XML_DTD_NODE)) {
1093 				php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Document types must be the first child in a document", /* strict */ true);
1094 				return FAILURE;
1095 			}
1096 		}
1097 		/* DocumentType */
1098 		else if (node->type == XML_DTD_NODE) {
1099 			/* parent has a doctype child that is not child, or an element is preceding child. */
1100 			xmlDocPtr doc = (xmlDocPtr) parent;
1101 			if (doc->intSubset != (xmlDtdPtr) child || php_dom_has_sibling_preceding_node(child, XML_ELEMENT_NODE)) {
1102 				php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Document types must be the first child in a document", /* strict */ true);
1103 				return FAILURE;
1104 			}
1105 		}
1106 	}
1107 
1108 	/* Steps 7 and onward perform the removal and insertion, and also track changes for mutation records.
1109 	 * We don't implement mutation records so we can just skip straight to the replace part. */
1110 
1111 	return SUCCESS;
1112 }
1113 
1114 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-785887307
1115 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-replacechild
1116 Since:
1117 */
dom_node_replace_child(INTERNAL_FUNCTION_PARAMETERS,bool modern)1118 static void dom_node_replace_child(INTERNAL_FUNCTION_PARAMETERS, bool modern)
1119 {
1120 	zval *id, *newnode, *oldnode;
1121 	xmlNodePtr newchild, oldchild, nodep;
1122 	dom_object *intern, *newchildobj, *oldchildobj;
1123 
1124 	id = ZEND_THIS;
1125 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "OO", &newnode, dom_get_node_ce(modern), &oldnode, dom_get_node_ce(modern)) == FAILURE) {
1126 		RETURN_THROWS();
1127 	}
1128 
1129 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1130 
1131 	DOM_GET_OBJ(newchild, newnode, xmlNodePtr, newchildobj);
1132 	DOM_GET_OBJ(oldchild, oldnode, xmlNodePtr, oldchildobj);
1133 
1134 	bool stricterror = dom_get_strict_error(intern->document);
1135 
1136 	if (newchild->doc != nodep->doc && newchild->doc != NULL) {
1137 		php_dom_throw_error(WRONG_DOCUMENT_ERR, stricterror);
1138 		RETURN_FALSE;
1139 	}
1140 
1141 	if (modern) {
1142 		if (dom_replace_node_validity_checks(nodep, newchild, oldchild) != SUCCESS) {
1143 			RETURN_THROWS();
1144 		}
1145 	} else {
1146 		if (!dom_node_children_valid(nodep)) {
1147 			RETURN_FALSE;
1148 		}
1149 
1150 		if (!nodep->children) {
1151 			RETURN_FALSE;
1152 		}
1153 
1154 		if (dom_node_is_read_only(nodep) == SUCCESS ||
1155 			(newchild->parent != NULL && dom_node_is_read_only(newchild->parent) == SUCCESS)) {
1156 			php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
1157 			RETURN_FALSE;
1158 		}
1159 
1160 		if (dom_hierarchy(nodep, newchild) == FAILURE) {
1161 			php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
1162 			RETURN_FALSE;
1163 		}
1164 
1165 		if (oldchild->parent != nodep) {
1166 			php_dom_throw_error(NOT_FOUND_ERR, stricterror);
1167 			RETURN_FALSE;
1168 		}
1169 	}
1170 
1171 	if (newchild->type == XML_DOCUMENT_FRAG_NODE) {
1172 		xmlNodePtr prevsib, nextsib;
1173 		prevsib = oldchild->prev;
1174 		nextsib = oldchild->next;
1175 
1176 		xmlUnlinkNode(oldchild);
1177 
1178 		xmlNodePtr last = newchild->last;
1179 		newchild = _php_dom_insert_fragment(nodep, prevsib, nextsib, newchild, intern);
1180 		if (newchild && !modern) {
1181 			dom_reconcile_ns_list(nodep->doc, newchild, last);
1182 		}
1183 	} else if (oldchild != newchild) {
1184 		xmlDtdPtr intSubset = xmlGetIntSubset(nodep->doc);
1185 		bool replacedoctype = (intSubset == (xmlDtd *) oldchild);
1186 
1187 		if (newchild->doc == NULL && nodep->doc != NULL) {
1188 			xmlSetTreeDoc(newchild, nodep->doc);
1189 			newchildobj->document = intern->document;
1190 			php_libxml_increment_doc_ref((php_libxml_node_object *)newchildobj, NULL);
1191 		}
1192 		xmlReplaceNode(oldchild, newchild);
1193 		if (!modern) {
1194 			dom_reconcile_ns(nodep->doc, newchild);
1195 		}
1196 
1197 		if (replacedoctype) {
1198 			nodep->doc->intSubset = (xmlDtd *) newchild;
1199 		}
1200 	}
1201 	php_libxml_invalidate_node_list_cache(intern->document);
1202 	DOM_RET_OBJ(oldchild, intern);
1203 }
1204 
PHP_METHOD(DOMNode,replaceChild)1205 PHP_METHOD(DOMNode, replaceChild)
1206 {
1207 	dom_node_replace_child(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1208 }
1209 
PHP_METHOD(DOM_Node,replaceChild)1210 PHP_METHOD(DOM_Node, replaceChild)
1211 {
1212 	dom_node_replace_child(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1213 }
1214 /* }}} end dom_node_replace_child */
1215 
1216 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1734834066
1217 Since:
1218 */
dom_node_remove_child(INTERNAL_FUNCTION_PARAMETERS,zend_class_entry * node_ce)1219 static void dom_node_remove_child(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *node_ce)
1220 {
1221 	zval *id, *node;
1222 	xmlNodePtr child, nodep;
1223 	dom_object *intern, *childobj;
1224 
1225 	id = ZEND_THIS;
1226 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node, node_ce) == FAILURE) {
1227 		RETURN_THROWS();
1228 	}
1229 
1230 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1231 
1232 	if (!dom_node_children_valid(nodep)) {
1233 		RETURN_FALSE;
1234 	}
1235 
1236 	DOM_GET_OBJ(child, node, xmlNodePtr, childobj);
1237 
1238 	bool stricterror = dom_get_strict_error(intern->document);
1239 
1240 	if (dom_node_is_read_only(nodep) == SUCCESS ||
1241 		(child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
1242 		php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
1243 		RETURN_FALSE;
1244 	}
1245 
1246 	if (!nodep->children || child->parent != nodep) {
1247 		php_dom_throw_error(NOT_FOUND_ERR, stricterror);
1248 		RETURN_FALSE;
1249 	}
1250 
1251 	xmlUnlinkNode(child);
1252 	php_libxml_invalidate_node_list_cache(intern->document);
1253 	DOM_RET_OBJ(child, intern);
1254 }
1255 
PHP_METHOD(DOMNode,removeChild)1256 PHP_METHOD(DOMNode, removeChild)
1257 {
1258 	dom_node_remove_child(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_node_class_entry);
1259 }
1260 
PHP_METHOD(DOM_Node,removeChild)1261 PHP_METHOD(DOM_Node, removeChild)
1262 {
1263 	dom_node_remove_child(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_modern_node_class_entry);
1264 }
1265 /* }}} end dom_node_remove_child */
1266 
1267 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-184E7107
1268 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-appendchild
1269 Since:
1270 */
dom_node_append_child_legacy(zval * return_value,dom_object * intern,dom_object * childobj,xmlNodePtr nodep,xmlNodePtr child)1271 static void dom_node_append_child_legacy(zval *return_value, dom_object *intern, dom_object *childobj, xmlNodePtr nodep, xmlNodePtr child)
1272 {
1273 	xmlNodePtr new_child = NULL;
1274 
1275 	if (!dom_node_children_valid(nodep)) {
1276 		RETURN_FALSE;
1277 	}
1278 
1279 	bool stricterror = dom_get_strict_error(intern->document);
1280 
1281 	if (dom_node_is_read_only(nodep) == SUCCESS ||
1282 		(child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
1283 		php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
1284 		RETURN_FALSE;
1285 	}
1286 
1287 	if (dom_hierarchy(nodep, child) == FAILURE) {
1288 		php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
1289 		RETURN_FALSE;
1290 	}
1291 
1292 	if (!(child->doc == NULL || child->doc == nodep->doc)) {
1293 		php_dom_throw_error(WRONG_DOCUMENT_ERR, stricterror);
1294 		RETURN_FALSE;
1295 	}
1296 
1297 	if (child->type == XML_DOCUMENT_FRAG_NODE && child->children == NULL) {
1298 		/* TODO Drop Warning? */
1299 		php_error_docref(NULL, E_WARNING, "Document Fragment is empty");
1300 		RETURN_FALSE;
1301 	}
1302 
1303 	if (child->doc == NULL && nodep->doc != NULL) {
1304 		childobj->document = intern->document;
1305 		php_libxml_increment_doc_ref((php_libxml_node_object *)childobj, NULL);
1306 	}
1307 
1308 	if (child->parent != NULL){
1309 		xmlUnlinkNode(child);
1310 	}
1311 
1312 	if (child->type == XML_TEXT_NODE && nodep->last != NULL && nodep->last->type == XML_TEXT_NODE) {
1313 		child->parent = nodep;
1314 		if (child->doc == NULL) {
1315 			xmlSetTreeDoc(child, nodep->doc);
1316 		}
1317 		new_child = child;
1318 		if (nodep->children == NULL) {
1319 			nodep->children = child;
1320 			nodep->last = child;
1321 		} else {
1322 			child = nodep->last;
1323 			child->next = new_child;
1324 			new_child->prev = child;
1325 			nodep->last = new_child;
1326 		}
1327 	} else 	if (child->type == XML_ATTRIBUTE_NODE) {
1328 		xmlAttrPtr lastattr;
1329 
1330 		if (child->ns == NULL)
1331 			lastattr = xmlHasProp(nodep, child->name);
1332 		else
1333 			lastattr = xmlHasNsProp(nodep, child->name, child->ns->href);
1334 		if (lastattr != NULL && lastattr->type != XML_ATTRIBUTE_DECL) {
1335 			if (lastattr != (xmlAttrPtr) child) {
1336 				xmlUnlinkNode((xmlNodePtr) lastattr);
1337 				php_libxml_node_free_resource((xmlNodePtr) lastattr);
1338 			}
1339 		}
1340 		new_child = xmlAddChild(nodep, child);
1341 		if (UNEXPECTED(new_child == NULL)) {
1342 			goto cannot_add;
1343 		}
1344 		php_dom_reconcile_attribute_namespace_after_insertion((xmlAttrPtr) new_child);
1345 	} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
1346 		xmlNodePtr last = child->last;
1347 		new_child = _php_dom_insert_fragment(nodep, nodep->last, NULL, child, intern);
1348 		dom_reconcile_ns_list(nodep->doc, new_child, last);
1349 	} else if (child->type == XML_DTD_NODE) {
1350 		if (nodep->doc->intSubset != NULL) {
1351 			php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "A document may only contain one document type", stricterror);
1352 			RETURN_FALSE;
1353 		}
1354 		new_child = xmlAddChild(nodep, child);
1355 		if (UNEXPECTED(new_child == NULL)) {
1356 			goto cannot_add;
1357 		}
1358 		nodep->doc->intSubset = (xmlDtdPtr) new_child;
1359 	} else {
1360 		new_child = xmlAddChild(nodep, child);
1361 		if (UNEXPECTED(new_child == NULL)) {
1362 			goto cannot_add;
1363 		}
1364 		dom_reconcile_ns(nodep->doc, new_child);
1365 	}
1366 
1367 	php_libxml_invalidate_node_list_cache(intern->document);
1368 
1369 	DOM_RET_OBJ(new_child, intern);
1370 	return;
1371 cannot_add:
1372 	php_dom_throw_error(INVALID_STATE_ERR, stricterror);
1373 	RETURN_FALSE;
1374 }
1375 /* }}} end dom_node_append_child */
1376 
PHP_METHOD(DOMNode,appendChild)1377 PHP_METHOD(DOMNode, appendChild)
1378 {
1379 	zval *node;
1380 	xmlNodePtr nodep, child;
1381 	dom_object *intern, *childobj;
1382 
1383 	ZEND_PARSE_PARAMETERS_START(1, 1)
1384 		Z_PARAM_OBJECT_OF_CLASS(node, dom_node_class_entry)
1385 	ZEND_PARSE_PARAMETERS_END();
1386 
1387 	DOM_GET_OBJ(nodep, ZEND_THIS, xmlNodePtr, intern);
1388 	DOM_GET_OBJ(child, node, xmlNodePtr, childobj);
1389 
1390 	dom_node_append_child_legacy(return_value, intern, childobj, nodep, child);
1391 }
1392 
PHP_METHOD(DOM_Node,appendChild)1393 PHP_METHOD(DOM_Node, appendChild)
1394 {
1395 	zval *node;
1396 	xmlNodePtr nodep, child;
1397 	dom_object *intern, *childobj;
1398 
1399 	ZEND_PARSE_PARAMETERS_START(1, 1)
1400 		Z_PARAM_OBJECT_OF_CLASS(node, dom_modern_node_class_entry)
1401 	ZEND_PARSE_PARAMETERS_END();
1402 
1403 	DOM_GET_OBJ(nodep, ZEND_THIS, xmlNodePtr, intern);
1404 	DOM_GET_OBJ(child, node, xmlNodePtr, childobj);
1405 
1406 	/* Parent check from pre-insertion validation done here:
1407 	 * If parent is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException. */
1408 	if (php_dom_pre_insert_is_parent_invalid(nodep)) {
1409 		php_dom_throw_error(HIERARCHY_REQUEST_ERR, /* strict */ true);
1410 		RETURN_THROWS();
1411 	}
1412 	/* Append, this doesn't do the parent check so we do it here. */
1413 	php_libxml_invalidate_node_list_cache(intern->document);
1414 	php_dom_node_append(intern->document, child, nodep);
1415 	DOM_RET_OBJ(child, intern);
1416 }
1417 
1418 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-810594187
1419 Since:
1420 */
PHP_METHOD(DOMNode,hasChildNodes)1421 PHP_METHOD(DOMNode, hasChildNodes)
1422 {
1423 	zval *id;
1424 	xmlNode *nodep;
1425 	dom_object *intern;
1426 
1427 	id = ZEND_THIS;
1428 	if (zend_parse_parameters_none() == FAILURE) {
1429 		RETURN_THROWS();
1430 	}
1431 
1432 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1433 
1434 	if (!dom_node_children_valid(nodep)) {
1435 		RETURN_FALSE;
1436 	}
1437 
1438 	if (nodep->children) {
1439 		RETURN_TRUE;
1440 	} else {
1441 		RETURN_FALSE;
1442 	}
1443 }
1444 /* }}} end dom_node_has_child_nodes */
1445 
1446 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-3A0ED0A4
1447 Since:
1448 */
PHP_METHOD(DOMNode,cloneNode)1449 PHP_METHOD(DOMNode, cloneNode)
1450 {
1451 	zval *id;
1452 	xmlNode *n, *node;
1453 	dom_object *intern;
1454 	bool recursive = 0;
1455 
1456 	id = ZEND_THIS;
1457 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &recursive) == FAILURE) {
1458 		RETURN_THROWS();
1459 	}
1460 
1461 	DOM_GET_OBJ(n, id, xmlNodePtr, intern);
1462 
1463 	php_dom_libxml_ns_mapper *ns_mapper = NULL;
1464 	bool clone_document = n->type == XML_DOCUMENT_NODE || n->type == XML_HTML_DOCUMENT_NODE;
1465 	if (php_dom_follow_spec_intern(intern)) {
1466 		if (clone_document) {
1467 			ns_mapper = php_dom_libxml_ns_mapper_create();
1468 		} else {
1469 			ns_mapper = php_dom_get_ns_mapper(intern);
1470 		}
1471 	}
1472 
1473 	node = dom_clone_node(ns_mapper, n, n->doc, recursive);
1474 
1475 	if (!node) {
1476 		if (clone_document && ns_mapper != NULL) {
1477 			php_dom_libxml_ns_mapper_destroy(ns_mapper);
1478 		}
1479 		RETURN_FALSE;
1480 	}
1481 
1482 	/* If document cloned we want a new document proxy */
1483 	if (clone_document) {
1484 		dom_object *new_intern;
1485 		if (ns_mapper) {
1486 			/* We have the issue here that we can't create a modern node without an intern.
1487 			 * Fortunately, it's impossible to have a custom document class for the modern DOM (final base class),
1488 			 * so we can solve this by invoking the instantiation helper directly. */
1489 			zend_class_entry *ce = n->type == XML_DOCUMENT_NODE ? dom_xml_document_class_entry : dom_html_document_class_entry;
1490 			new_intern = php_dom_instantiate_object_helper(return_value, ce, node, NULL);
1491 		} else {
1492 			DOM_RET_OBJ(node, NULL);
1493 			new_intern = Z_DOMOBJ_P(return_value);
1494 		}
1495 		php_dom_update_document_after_clone(intern, n, new_intern, node);
1496 		ZEND_ASSERT(new_intern->document->private_data == NULL);
1497 		new_intern->document->private_data = php_dom_libxml_ns_mapper_header(ns_mapper);
1498 	} else {
1499 		if (node->type == XML_ATTRIBUTE_NODE && n->ns != NULL && node->ns == NULL) {
1500 			/* Let reconciliation deal with this. The lifetime of the namespace poses no problem
1501 			 * because we're increasing the refcount of the document proxy at the return.
1502 			 * libxml2 doesn't set the ns because it can't know that this is safe. */
1503 			node->ns = n->ns;
1504 		}
1505 
1506 		DOM_RET_OBJ(node, intern);
1507 	}
1508 }
1509 /* }}} end dom_node_clone_node */
1510 
1511 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-normalize
1512 Since:
1513 */
PHP_METHOD(DOMNode,normalize)1514 PHP_METHOD(DOMNode, normalize)
1515 {
1516 	zval *id;
1517 	xmlNode *nodep;
1518 	dom_object *intern;
1519 
1520 	id = ZEND_THIS;
1521 	if (zend_parse_parameters_none() == FAILURE) {
1522 		RETURN_THROWS();
1523 	}
1524 
1525 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1526 
1527 	if (php_dom_follow_spec_intern(intern)) {
1528 		php_dom_normalize_modern(nodep);
1529 	} else {
1530 		php_dom_normalize_legacy(nodep);
1531 	}
1532 }
1533 /* }}} end dom_node_normalize */
1534 
1535 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Level-2-Core-Node-supports
1536 Since: DOM Level 2
1537 */
PHP_METHOD(DOMNode,isSupported)1538 PHP_METHOD(DOMNode, isSupported)
1539 {
1540 	zend_string *feature, *version;
1541 
1542 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &feature, &version) == FAILURE) {
1543 		RETURN_THROWS();
1544 	}
1545 
1546 	RETURN_BOOL(dom_has_feature(feature, version));
1547 }
1548 /* }}} end dom_node_is_supported */
1549 
1550 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-NodeHasAttrs
1551 Since: DOM Level 2
1552 */
PHP_METHOD(DOMNode,hasAttributes)1553 PHP_METHOD(DOMNode, hasAttributes)
1554 {
1555 	zval *id;
1556 	xmlNode *nodep;
1557 	dom_object *intern;
1558 
1559 	id = ZEND_THIS;
1560 	if (zend_parse_parameters_none() == FAILURE) {
1561 		RETURN_THROWS();
1562 	}
1563 
1564 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1565 
1566 	if (nodep->type != XML_ELEMENT_NODE)
1567 		RETURN_FALSE;
1568 
1569 	if (nodep->properties) {
1570 		RETURN_TRUE;
1571 	} else {
1572 		RETURN_FALSE;
1573 	}
1574 }
1575 /* }}} end dom_node_has_attributes */
1576 
1577 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Node3-isSameNode
1578 Since: DOM Level 3
1579 */
dom_node_is_same_node(INTERNAL_FUNCTION_PARAMETERS,zval * node)1580 static void dom_node_is_same_node(INTERNAL_FUNCTION_PARAMETERS, zval *node)
1581 {
1582 	zval *id;
1583 	xmlNodePtr nodeotherp, nodep;
1584 	dom_object *intern, *nodeotherobj;
1585 
1586 	DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
1587 
1588 	DOM_GET_OBJ(nodeotherp, node, xmlNodePtr, nodeotherobj);
1589 
1590 	if (nodep == nodeotherp) {
1591 		RETURN_TRUE;
1592 	} else {
1593 		RETURN_FALSE;
1594 	}
1595 }
1596 
PHP_METHOD(DOMNode,isSameNode)1597 PHP_METHOD(DOMNode, isSameNode)
1598 {
1599 	zval *node;
1600 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node, dom_node_class_entry) != SUCCESS) {
1601 		RETURN_THROWS();
1602 	}
1603 
1604 	dom_node_is_same_node(INTERNAL_FUNCTION_PARAM_PASSTHRU, node);
1605 }
1606 
PHP_METHOD(DOM_Node,isSameNode)1607 PHP_METHOD(DOM_Node, isSameNode)
1608 {
1609 	zval *node;
1610 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O!", &node, dom_modern_node_class_entry) != SUCCESS) {
1611 		RETURN_THROWS();
1612 	}
1613 
1614 	if (node == NULL) {
1615 		RETURN_FALSE;
1616 	}
1617 
1618 	dom_node_is_same_node(INTERNAL_FUNCTION_PARAM_PASSTHRU, node);
1619 }
1620 /* }}} end dom_node_is_same_node */
1621 
php_dom_node_is_content_equal(const xmlNode * this,const xmlNode * other)1622 static bool php_dom_node_is_content_equal(const xmlNode *this, const xmlNode *other)
1623 {
1624 	xmlChar *this_content = xmlNodeGetContent(this);
1625 	xmlChar *other_content = xmlNodeGetContent(other);
1626 	bool result = xmlStrEqual(this_content, other_content);
1627 	xmlFree(this_content);
1628 	xmlFree(other_content);
1629 	return result;
1630 }
1631 
php_dom_node_is_ns_uri_equal(const xmlNode * this,const xmlNode * other)1632 static bool php_dom_node_is_ns_uri_equal(const xmlNode *this, const xmlNode *other)
1633 {
1634 	const xmlChar *this_ns = this->ns ? this->ns->href : NULL;
1635 	const xmlChar *other_ns = other->ns ? other->ns->href : NULL;
1636 	return xmlStrEqual(this_ns, other_ns);
1637 }
1638 
php_dom_node_is_ns_prefix_equal(const xmlNode * this,const xmlNode * other)1639 static bool php_dom_node_is_ns_prefix_equal(const xmlNode *this, const xmlNode *other)
1640 {
1641 	const xmlChar *this_ns = this->ns ? this->ns->prefix : NULL;
1642 	const xmlChar *other_ns = other->ns ? other->ns->prefix : NULL;
1643 	return xmlStrEqual(this_ns, other_ns);
1644 }
1645 
1646 static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other, bool spec_compliant);
1647 
1648 #define PHP_DOM_FUNC_CAT(prefix, suffix) prefix##_##suffix
1649 /* xmlNode and xmlNs have incompatible struct layouts, i.e. the next field is in a different offset */
1650 #define PHP_DOM_DEFINE_LIST_COUNTER_HELPER(type)																							\
1651 	static size_t PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(const type *node)													\
1652 	{																																		\
1653 		size_t counter = 0;																													\
1654 		while (node) {																														\
1655 			counter++;																														\
1656 			node = node->next;																												\
1657 		}																																	\
1658 		return counter;																														\
1659 	}
1660 #define PHP_DOM_DEFINE_LIST_EQUALITY_ORDERED_HELPER(type)																					\
1661 	static bool PHP_DOM_FUNC_CAT(php_dom_node_list_equality_check_ordered, type)(const type *list1, const type *list2, bool spec_compliant)	\
1662 	{																																		\
1663 		size_t count = PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list1);															\
1664 		if (count != PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list2)) {															\
1665 			return false;																													\
1666 		}																																	\
1667 		for (size_t i = 0; i < count; i++) {																								\
1668 			if (!php_dom_node_is_equal_node((const xmlNode *) list1, (const xmlNode *) list2, spec_compliant)) {							\
1669 				return false;																												\
1670 			}																																\
1671 			list1 = list1->next;																											\
1672 			list2 = list2->next;																											\
1673 		}																																	\
1674 		return true;																														\
1675 	}
1676 #define PHP_DOM_DEFINE_LIST_EQUALITY_UNORDERED_HELPER(type)																					\
1677 	static bool PHP_DOM_FUNC_CAT(php_dom_node_list_equality_check_unordered, type)(const type *list1, const type *list2, bool spec_compliant)\
1678 	{																																		\
1679 		size_t count = PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list1);															\
1680 		if (count != PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list2)) {															\
1681 			return false;																													\
1682 		}																																	\
1683 		for (const type *n1 = list1; n1 != NULL; n1 = n1->next) {																			\
1684 			bool found = false;																												\
1685 			for (const type *n2 = list2; n2 != NULL && !found; n2 = n2->next) {																\
1686 				if (php_dom_node_is_equal_node((const xmlNode *) n1, (const xmlNode *) n2, spec_compliant)) {								\
1687 					found = true;																											\
1688 				}																															\
1689 			}																																\
1690 			if (!found) {																													\
1691 				return false;																												\
1692 			}																																\
1693 		}																																	\
1694 		return true;																														\
1695 	}
1696 
1697 PHP_DOM_DEFINE_LIST_COUNTER_HELPER(xmlNode)
PHP_DOM_DEFINE_LIST_COUNTER_HELPER(xmlNs)1698 PHP_DOM_DEFINE_LIST_COUNTER_HELPER(xmlNs)
1699 PHP_DOM_DEFINE_LIST_EQUALITY_ORDERED_HELPER(xmlNode)
1700 PHP_DOM_DEFINE_LIST_EQUALITY_UNORDERED_HELPER(xmlNode)
1701 PHP_DOM_DEFINE_LIST_EQUALITY_UNORDERED_HELPER(xmlNs)
1702 
1703 static bool php_dom_is_equal_attr(const xmlAttr *this_attr, const xmlAttr *other_attr)
1704 {
1705 	ZEND_ASSERT(this_attr != NULL);
1706 	ZEND_ASSERT(other_attr != NULL);
1707 	return xmlStrEqual(this_attr->name, other_attr->name)
1708 		&& php_dom_node_is_ns_uri_equal((const xmlNode *) this_attr, (const xmlNode *) other_attr)
1709 		&& php_dom_node_is_content_equal((const xmlNode *) this_attr, (const xmlNode *) other_attr);
1710 }
1711 
php_dom_node_is_equal_node(const xmlNode * this,const xmlNode * other,bool spec_compliant)1712 static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other, bool spec_compliant)
1713 {
1714 	ZEND_ASSERT(this != NULL);
1715 	ZEND_ASSERT(other != NULL);
1716 
1717 	if (this->type != other->type) {
1718 		return false;
1719 	}
1720 
1721 	/* Notes:
1722 	 *   - XML_DOCUMENT_TYPE_NODE is no longer created by libxml2, we only have to support XML_DTD_NODE.
1723 	 *   - element and attribute declarations are not exposed as nodes in DOM, so no comparison is needed for those. */
1724 	if (this->type == XML_ELEMENT_NODE) {
1725 		return xmlStrEqual(this->name, other->name)
1726 			&& php_dom_node_is_ns_prefix_equal(this, other)
1727 			&& php_dom_node_is_ns_uri_equal(this, other)
1728 			/* Check attributes first, then namespace declarations, then children */
1729 			&& php_dom_node_list_equality_check_unordered_xmlNode((const xmlNode *) this->properties, (const xmlNode *) other->properties, spec_compliant)
1730 			&& (spec_compliant || php_dom_node_list_equality_check_unordered_xmlNs(this->nsDef, other->nsDef, false))
1731 			&& php_dom_node_list_equality_check_ordered_xmlNode(this->children, other->children, spec_compliant);
1732 	} else if (this->type == XML_DTD_NODE) {
1733 		/* Note: in the living spec entity declarations and notations are no longer compared because they're considered obsolete. */
1734 		const xmlDtd *this_dtd = (const xmlDtd *) this;
1735 		const xmlDtd *other_dtd = (const xmlDtd *) other;
1736 		return xmlStrEqual(this_dtd->name, other_dtd->name)
1737 			&& xmlStrEqual(this_dtd->ExternalID, other_dtd->ExternalID)
1738 			&& xmlStrEqual(this_dtd->SystemID, other_dtd->SystemID);
1739 	} else if (this->type == XML_PI_NODE) {
1740 		return xmlStrEqual(this->name, other->name) && xmlStrEqual(this->content, other->content);
1741 	} else if (this->type == XML_TEXT_NODE || this->type == XML_COMMENT_NODE || this->type == XML_CDATA_SECTION_NODE) {
1742 		return xmlStrEqual(this->content, other->content);
1743 	} else if (this->type == XML_ATTRIBUTE_NODE) {
1744 		const xmlAttr *this_attr = (const xmlAttr *) this;
1745 		const xmlAttr *other_attr = (const xmlAttr *) other;
1746 		return php_dom_is_equal_attr(this_attr, other_attr);
1747 	} else if (this->type == XML_ENTITY_REF_NODE) {
1748 		return xmlStrEqual(this->name, other->name);
1749 	} else if (this->type == XML_ENTITY_DECL || this->type == XML_NOTATION_NODE || this->type == XML_ENTITY_NODE) {
1750 		const xmlEntity *this_entity = (const xmlEntity *) this;
1751 		const xmlEntity *other_entity = (const xmlEntity *) other;
1752 		return this_entity->etype == other_entity->etype
1753 			&& xmlStrEqual(this_entity->name, other_entity->name)
1754 			&& xmlStrEqual(this_entity->ExternalID, other_entity->ExternalID)
1755 			&& xmlStrEqual(this_entity->SystemID, other_entity->SystemID)
1756 			&& php_dom_node_is_content_equal(this, other);
1757 	} else if (this->type == XML_NAMESPACE_DECL) {
1758 		const xmlNs *this_ns = (const xmlNs *) this;
1759 		const xmlNs *other_ns = (const xmlNs *) other;
1760 		return xmlStrEqual(this_ns->prefix, other_ns->prefix) && xmlStrEqual(this_ns->href, other_ns->href);
1761 	} else if (this->type == XML_DOCUMENT_FRAG_NODE || this->type == XML_HTML_DOCUMENT_NODE || this->type == XML_DOCUMENT_NODE) {
1762 		return php_dom_node_list_equality_check_ordered_xmlNode(this->children, other->children, spec_compliant);
1763 	}
1764 
1765 	return false;
1766 }
1767 
1768 /* {{{ URL: https://dom.spec.whatwg.org/#dom-node-isequalnode (for everything still in the living spec)
1769 *      URL: https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/DOM3-Core.html#core-Node3-isEqualNode (for old nodes removed from the living spec)
1770 Since: DOM Level 3
1771 */
dom_node_is_equal_node_common(INTERNAL_FUNCTION_PARAMETERS,bool modern)1772 static void dom_node_is_equal_node_common(INTERNAL_FUNCTION_PARAMETERS, bool modern)
1773 {
1774 	zval *id, *node;
1775 	xmlNodePtr otherp, nodep;
1776 	dom_object *intern;
1777 
1778 	id = ZEND_THIS;
1779 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O!", &node, dom_get_node_ce(modern)) == FAILURE) {
1780 		RETURN_THROWS();
1781 	}
1782 
1783 	if (node == NULL) {
1784 		RETURN_FALSE;
1785 	}
1786 
1787 	DOM_GET_OBJ(otherp, node, xmlNodePtr, intern);
1788 	DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
1789 
1790 	if (nodep == otherp) {
1791 		RETURN_TRUE;
1792 	}
1793 
1794 	/* Empty fragments/documents only match if they're both empty */
1795 	if (nodep == NULL || otherp == NULL) {
1796 		RETURN_BOOL(nodep == NULL && otherp == NULL);
1797 	}
1798 
1799 	RETURN_BOOL(php_dom_node_is_equal_node(nodep, otherp, modern));
1800 }
1801 
PHP_METHOD(DOMNode,isEqualNode)1802 PHP_METHOD(DOMNode, isEqualNode)
1803 {
1804 	dom_node_is_equal_node_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1805 }
1806 
PHP_METHOD(DOM_Node,isEqualNode)1807 PHP_METHOD(DOM_Node, isEqualNode)
1808 {
1809 	dom_node_is_equal_node_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1810 }
1811 /* }}} end DOMNode::isEqualNode */
1812 
1813 /* https://dom.spec.whatwg.org/#locate-a-namespace-prefix */
dom_locate_a_namespace_prefix(xmlNodePtr elem,const char * uri)1814 static const xmlChar *dom_locate_a_namespace_prefix(xmlNodePtr elem, const char *uri)
1815 {
1816 	do {
1817 		/* 1. If element’s namespace is namespace and its namespace prefix is non-null, then return its namespace prefix. */
1818 		if (elem->ns != NULL && elem->ns->prefix != NULL && xmlStrEqual(elem->ns->href, BAD_CAST uri)) {
1819 			return elem->ns->prefix;
1820 		}
1821 
1822 		/* 2. If element has an attribute whose namespace prefix is "xmlns" and value is namespace,
1823 		*     then return element’s first such attribute’s local name. */
1824 		for (xmlAttrPtr attr = elem->properties; attr != NULL; attr = attr->next) {
1825 			if (attr->ns != NULL && attr->children != NULL
1826 				&& xmlStrEqual(attr->ns->prefix, BAD_CAST "xmlns") && xmlStrEqual(attr->children->content, BAD_CAST uri)) {
1827 				return attr->name;
1828 			}
1829 		}
1830 
1831 		/* 3. If element’s parent element is not null, then return the result of running locate a namespace prefix on that element using namespace. */
1832 		elem = elem->parent;
1833 	} while (elem != NULL && elem->type == XML_ELEMENT_NODE);
1834 
1835 	/* 4. Return null. */
1836 	return NULL;
1837 }
1838 
1839 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Node3-lookupNamespacePrefix
1840 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-lookupprefix
1841 Since: DOM Level 3
1842 */
dom_node_lookup_prefix(INTERNAL_FUNCTION_PARAMETERS,bool modern)1843 static void dom_node_lookup_prefix(INTERNAL_FUNCTION_PARAMETERS, bool modern)
1844 {
1845 	zval *id;
1846 	xmlNodePtr nodep, lookupp = NULL;
1847 	dom_object *intern;
1848 	xmlNsPtr nsptr;
1849 	size_t uri_len = 0;
1850 	char *uri;
1851 
1852 	id = ZEND_THIS;
1853 	if (zend_parse_parameters(ZEND_NUM_ARGS(), modern ? "s!" : "s", &uri, &uri_len) == FAILURE) {
1854 		RETURN_THROWS();
1855 	}
1856 
1857 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1858 
1859 	/* 1. If namespace is null or the empty string, then return null. */
1860 	if (uri_len > 0) {
1861 		/* 2. Switch on the interface this implements: */
1862 		switch (nodep->type) {
1863 			case XML_ELEMENT_NODE:
1864 				lookupp = nodep;
1865 				break;
1866 			case XML_DOCUMENT_NODE:
1867 			case XML_HTML_DOCUMENT_NODE:
1868 				lookupp = xmlDocGetRootElement((xmlDocPtr) nodep);
1869 				break;
1870 			case XML_ENTITY_NODE :
1871 			case XML_NOTATION_NODE:
1872 			case XML_DOCUMENT_FRAG_NODE:
1873 			case XML_DOCUMENT_TYPE_NODE:
1874 			case XML_DTD_NODE:
1875 				RETURN_NULL();
1876 				break;
1877 			default:
1878 				lookupp = nodep->parent;
1879 		}
1880 
1881 		if (lookupp != NULL) {
1882 			if (modern) {
1883 				const char * result = (const char *) dom_locate_a_namespace_prefix(lookupp, uri);
1884 				if (result != NULL) {
1885 					RETURN_STRING(result);
1886 				}
1887 			} else {
1888 				nsptr = xmlSearchNsByHref(lookupp->doc, lookupp, BAD_CAST uri);
1889 				if (nsptr && nsptr->prefix != NULL) {
1890 					RETURN_STRING((const char *) nsptr->prefix);
1891 				}
1892 			}
1893 		}
1894 	}
1895 
1896 	RETURN_NULL();
1897 }
1898 
PHP_METHOD(DOMNode,lookupPrefix)1899 PHP_METHOD(DOMNode, lookupPrefix)
1900 {
1901 	dom_node_lookup_prefix(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1902 }
1903 
PHP_METHOD(DOM_Node,lookupPrefix)1904 PHP_METHOD(DOM_Node, lookupPrefix)
1905 {
1906 	dom_node_lookup_prefix(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1907 }
1908 /* }}} end dom_node_lookup_prefix */
1909 
1910 /* https://dom.spec.whatwg.org/#locate-a-namespace */
dom_locate_a_namespace(xmlNodePtr node,const zend_string * prefix)1911 static const char *dom_locate_a_namespace(xmlNodePtr node, const zend_string *prefix)
1912 {
1913 	/* switch on the interface node implements: */
1914 	if (node->type == XML_ELEMENT_NODE) {
1915 		if (prefix != NULL) {
1916 			/* 1. If prefix is "xml", then return the XML namespace. */
1917 			if (zend_string_equals_literal_ci(prefix, "xml")) {
1918 				return DOM_XML_NS_URI;
1919 			}
1920 
1921 			/* 2. If prefix is "xmlns", then return the XMLNS namespace. */
1922 			if (zend_string_equals_literal_ci(prefix, "xmlns")) {
1923 				return DOM_XMLNS_NS_URI;
1924 			}
1925 		}
1926 
1927 		do {
1928 			/* 3. If its namespace is non-null and its namespace prefix is prefix, then return namespace. */
1929 			if (node->ns != NULL && xmlStrEqual(node->ns->prefix, BAD_CAST (prefix ? ZSTR_VAL(prefix) : NULL))) {
1930 				return (const char *) node->ns->href;
1931 			}
1932 
1933 			/* 4. If it has an attribute whose namespace is the XMLNS namespace, namespace prefix is "xmlns", and local name is prefix,
1934 			*     or if prefix is null and it has an attribute whose namespace is the XMLNS namespace, namespace prefix is null, and local name is "xmlns",
1935 			*     then return its value if it is not the empty string, and null otherwise. */
1936 			for (xmlAttrPtr attr = node->properties; attr != NULL; attr = attr->next) {
1937 				if (attr->ns == NULL || !php_dom_ns_is_fast_ex(attr->ns, php_dom_ns_is_xmlns_magic_token)) {
1938 					continue;
1939 				}
1940 				if ((prefix != NULL && xmlStrEqual(attr->ns->prefix, BAD_CAST "xmlns") && xmlStrEqual(attr->name, BAD_CAST ZSTR_VAL(prefix)))
1941 					|| (prefix == NULL && attr->ns->prefix == NULL && xmlStrEqual(attr->name, BAD_CAST "xmlns"))) {
1942 					if (attr->children != NULL && attr->children->content[0] != '\0') {
1943 						return (const char *) attr->children->content;
1944 					} else {
1945 						return NULL;
1946 					}
1947 				}
1948 			}
1949 
1950 			/* 5. If its parent element is null, then return null. */
1951 			if (node->parent == NULL || node->parent->type != XML_ELEMENT_NODE) {
1952 				return NULL;
1953 			}
1954 
1955 			/* 6. Return the result of running locate a namespace on its parent element using prefix. */
1956 			node = node->parent;
1957 		} while (true);
1958 	} else if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) {
1959 		/* 1. If its document element is null, then return null. */
1960 		node = xmlDocGetRootElement((xmlDocPtr) node);
1961 		if (UNEXPECTED(node == NULL)) {
1962 			return NULL;
1963 		}
1964 
1965 		/* 2. Return the result of running locate a namespace on its document element using prefix. */
1966 		return dom_locate_a_namespace(node, prefix);
1967 	} else if (node->type == XML_DTD_NODE || node->type == XML_DOCUMENT_FRAG_NODE) {
1968 		return NULL;
1969 	} else {
1970 		/* 1. If its element is null, then return null / If its parent element is null, then return null. */
1971 		if (node->parent == NULL || node->parent->type != XML_ELEMENT_NODE) {
1972 			return NULL;
1973 		}
1974 
1975 		/* 2. Return the result of running locate a namespace on its element using prefix. */
1976 		return dom_locate_a_namespace(node->parent, prefix);
1977 	}
1978 }
1979 
1980 /* {{{ URL: http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-isDefaultNamespace
1981 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-isdefaultnamespace
1982 Since: DOM Level 3
1983 */
PHP_METHOD(DOMNode,isDefaultNamespace)1984 PHP_METHOD(DOMNode, isDefaultNamespace)
1985 {
1986 	zval *id;
1987 	xmlNodePtr nodep;
1988 	dom_object *intern;
1989 	xmlNsPtr nsptr;
1990 	size_t uri_len = 0;
1991 	char *uri;
1992 
1993 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &uri, &uri_len) != SUCCESS) {
1994 		RETURN_THROWS();
1995 	}
1996 
1997 	DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
1998 
1999 	if (uri_len > 0) {
2000 		if (nodep->type == XML_DOCUMENT_NODE || nodep->type == XML_HTML_DOCUMENT_NODE) {
2001 			nodep = xmlDocGetRootElement((xmlDocPtr) nodep);
2002 			if (nodep == NULL) {
2003 				RETURN_FALSE;
2004 			}
2005 		}
2006 
2007 		nsptr = xmlSearchNs(nodep->doc, nodep, NULL);
2008 		if (nsptr && xmlStrEqual(nsptr->href, BAD_CAST uri)) {
2009 			RETURN_TRUE;
2010 		}
2011 	}
2012 
2013 	RETURN_FALSE;
2014 }
2015 
PHP_METHOD(DOM_Node,isDefaultNamespace)2016 PHP_METHOD(DOM_Node, isDefaultNamespace)
2017 {
2018 	zval *id;
2019 	xmlNodePtr nodep;
2020 	dom_object *intern;
2021 	size_t uri_len = 0;
2022 	char *uri;
2023 
2024 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!", &uri, &uri_len) != SUCCESS) {
2025 		RETURN_THROWS();
2026 	}
2027 
2028 	DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
2029 
2030 	if (uri_len == 0) {
2031 		uri = NULL;
2032 	}
2033 	const char *ns_uri = dom_locate_a_namespace(nodep, NULL);
2034 	RETURN_BOOL(xmlStrEqual(BAD_CAST uri, BAD_CAST ns_uri));
2035 }
2036 /* }}} end dom_node_is_default_namespace */
2037 
2038 /* {{{ URL: http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI
2039 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-lookupnamespaceuri
2040 Since: DOM Level 3
2041 */
PHP_METHOD(DOMNode,lookupNamespaceURI)2042 PHP_METHOD(DOMNode, lookupNamespaceURI)
2043 {
2044 	zval *id;
2045 	xmlNodePtr nodep;
2046 	dom_object *intern;
2047 	xmlNsPtr nsptr;
2048 	zend_string *prefix;
2049 
2050 	id = ZEND_THIS;
2051 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S!", &prefix) == FAILURE) {
2052 		RETURN_THROWS();
2053 	}
2054 
2055 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
2056 
2057 	if (php_dom_follow_spec_intern(intern)) {
2058 		if (prefix != NULL && ZSTR_LEN(prefix) == 0) {
2059 			prefix = NULL;
2060 		}
2061 		const char *ns_uri = dom_locate_a_namespace(nodep, prefix);
2062 		if (ns_uri == NULL) {
2063 			RETURN_NULL();
2064 		} else {
2065 			RETURN_STRING(ns_uri);
2066 		}
2067 	} else {
2068 		if (nodep->type == XML_DOCUMENT_NODE || nodep->type == XML_HTML_DOCUMENT_NODE) {
2069 			nodep = xmlDocGetRootElement((xmlDocPtr) nodep);
2070 			if (nodep == NULL) {
2071 				RETURN_NULL();
2072 			}
2073 		}
2074 
2075 		nsptr = xmlSearchNs(nodep->doc, nodep, BAD_CAST (prefix ? ZSTR_VAL(prefix) : NULL));
2076 		if (nsptr && nsptr->href != NULL) {
2077 			RETURN_STRING((char *) nsptr->href);
2078 		}
2079 	}
2080 
2081 	RETURN_NULL();
2082 }
2083 /* }}} end dom_node_lookup_namespace_uri */
2084 
dom_canonicalize_node_parent_lookup_cb(void * user_data,xmlNodePtr node,xmlNodePtr parent)2085 static int dom_canonicalize_node_parent_lookup_cb(void *user_data, xmlNodePtr node, xmlNodePtr parent)
2086 {
2087 	xmlNodePtr root = user_data;
2088 	/* We have to unroll the first iteration because node->parent
2089 	 * is not necessarily equal to parent due to libxml2 tree rules (ns decls out of the tree for example). */
2090 	if (node == root) {
2091 		return 1;
2092 	}
2093 	node = parent;
2094 	while (node != NULL) {
2095 		if (node == root) {
2096 			return 1;
2097 		}
2098 		node = node->parent;
2099 	}
2100 
2101 	return 0;
2102 }
2103 
dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS,int mode)2104 static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */
2105 {
2106 	zval *id;
2107 	zval *xpath_array=NULL, *ns_prefixes=NULL;
2108 	xmlNodePtr nodep;
2109 	xmlDocPtr docp;
2110 	xmlNodeSetPtr nodeset = NULL;
2111 	dom_object *intern;
2112 	bool exclusive=0, with_comments=0;
2113 	xmlChar **inclusive_ns_prefixes = NULL;
2114 	char *file = NULL;
2115 	int ret = -1;
2116 	size_t file_len = 0;
2117 	xmlOutputBufferPtr buf;
2118 	xmlXPathContextPtr ctxp=NULL;
2119 	xmlXPathObjectPtr xpathobjp=NULL;
2120 
2121 	id = ZEND_THIS;
2122 	if (mode == 0) {
2123 		if (zend_parse_parameters(ZEND_NUM_ARGS(),
2124 			"|bba!a!", &exclusive, &with_comments,
2125 			&xpath_array, &ns_prefixes) == FAILURE) {
2126 			RETURN_THROWS();
2127 		}
2128 	} else {
2129 		if (zend_parse_parameters(ZEND_NUM_ARGS(),
2130 			"s|bba!a!", &file, &file_len, &exclusive,
2131 			&with_comments, &xpath_array, &ns_prefixes) == FAILURE) {
2132 			RETURN_THROWS();
2133 		}
2134 	}
2135 
2136 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
2137 
2138 	docp = nodep->doc;
2139 
2140 	if (! docp) {
2141 		zend_throw_error(NULL, "Node must be associated with a document");
2142 		RETURN_THROWS();
2143 	}
2144 
2145 	bool simple_node_parent_lookup_callback = false;
2146 	if (xpath_array == NULL) {
2147 		/* Optimization: if the node is a document, all nodes may be included, no extra filtering or nodeset necessary. */
2148 		if (nodep->type != XML_DOCUMENT_NODE && nodep->type != XML_HTML_DOCUMENT_NODE) {
2149 			simple_node_parent_lookup_callback = true;
2150 		}
2151 	} else {
2152 		/*xpath query from xpath_array */
2153 		HashTable *ht = Z_ARRVAL_P(xpath_array);
2154 		zval *tmp;
2155 		char *xquery;
2156 
2157 		/* Find "query" key */
2158 		tmp = zend_hash_find(ht, ZSTR_KNOWN(ZEND_STR_QUERY));
2159 		if (!tmp) {
2160 			/* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */
2161 			zend_argument_value_error(3 + mode, "must have a \"query\" key");
2162 			RETURN_THROWS();
2163 		}
2164 		if (Z_TYPE_P(tmp) != IS_STRING) {
2165 			/* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */
2166 			zend_argument_type_error(3 + mode, "\"query\" option must be a string, %s given", zend_zval_value_name(tmp));
2167 			RETURN_THROWS();
2168 		}
2169 		xquery = Z_STRVAL_P(tmp);
2170 
2171 		ctxp = xmlXPathNewContext(docp);
2172 		ctxp->node = nodep;
2173 
2174 		tmp = zend_hash_str_find(ht, "namespaces", sizeof("namespaces")-1);
2175 		if (tmp && Z_TYPE_P(tmp) == IS_ARRAY && !HT_IS_PACKED(Z_ARRVAL_P(tmp))) {
2176 			zval *tmpns;
2177 			zend_string *prefix;
2178 
2179 			ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(tmp), prefix, tmpns) {
2180 				if (Z_TYPE_P(tmpns) == IS_STRING) {
2181 					if (prefix) {
2182 						xmlXPathRegisterNs(ctxp, BAD_CAST ZSTR_VAL(prefix), BAD_CAST Z_STRVAL_P(tmpns));
2183 					}
2184 				}
2185 			} ZEND_HASH_FOREACH_END();
2186 		}
2187 
2188 		xpathobjp = xmlXPathEvalExpression(BAD_CAST xquery, ctxp);
2189 		ctxp->node = NULL;
2190 		if (xpathobjp && xpathobjp->type == XPATH_NODESET) {
2191 			nodeset = xpathobjp->nodesetval;
2192 		} else {
2193 			if (xpathobjp) {
2194 				xmlXPathFreeObject(xpathobjp);
2195 			}
2196 			xmlXPathFreeContext(ctxp);
2197 			zend_throw_error(NULL, "XPath query did not return a nodeset");
2198 			RETURN_THROWS();
2199 		}
2200 	}
2201 
2202 	if (ns_prefixes != NULL) {
2203 		if (exclusive) {
2204 			zval *tmpns;
2205 			int nscount = 0;
2206 
2207 			inclusive_ns_prefixes = safe_emalloc(zend_hash_num_elements(Z_ARRVAL_P(ns_prefixes)) + 1,
2208 				sizeof(xmlChar *), 0);
2209 			ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(ns_prefixes), tmpns) {
2210 				if (Z_TYPE_P(tmpns) == IS_STRING) {
2211 					inclusive_ns_prefixes[nscount++] = BAD_CAST Z_STRVAL_P(tmpns);
2212 				}
2213 			} ZEND_HASH_FOREACH_END();
2214 			inclusive_ns_prefixes[nscount] = NULL;
2215 		} else {
2216 			php_error_docref(NULL, E_NOTICE,
2217 				"Inclusive namespace prefixes only allowed in exclusive mode.");
2218 		}
2219 	}
2220 
2221 	if (mode == 1) {
2222 		buf = xmlOutputBufferCreateFilename(file, NULL, 0);
2223 	} else {
2224 		buf = xmlAllocOutputBuffer(NULL);
2225 	}
2226 
2227 	if (buf != NULL) {
2228 		if (simple_node_parent_lookup_callback) {
2229 			ret = xmlC14NExecute(docp, dom_canonicalize_node_parent_lookup_cb, nodep, exclusive, inclusive_ns_prefixes, with_comments, buf);
2230 		} else {
2231 			ret = xmlC14NDocSaveTo(docp, nodeset, exclusive, inclusive_ns_prefixes, with_comments, buf);
2232 		}
2233 	}
2234 
2235 	if (inclusive_ns_prefixes != NULL) {
2236 		efree(inclusive_ns_prefixes);
2237 	}
2238 	if (xpathobjp != NULL) {
2239 		xmlXPathFreeObject(xpathobjp);
2240 	}
2241 	if (ctxp != NULL) {
2242 		xmlXPathFreeContext(ctxp);
2243 	}
2244 
2245 	if (buf == NULL || ret < 0) {
2246 		RETVAL_FALSE;
2247 	} else {
2248 		if (mode == 0) {
2249 			ret = xmlOutputBufferGetSize(buf);
2250 			if (ret > 0) {
2251 				RETVAL_STRINGL((char *) xmlOutputBufferGetContent(buf), ret);
2252 			} else {
2253 				RETVAL_EMPTY_STRING();
2254 			}
2255 		}
2256 	}
2257 
2258 	if (buf) {
2259 		int bytes;
2260 
2261 		bytes = xmlOutputBufferClose(buf);
2262 		if (mode == 1 && (ret >= 0)) {
2263 			RETURN_LONG(bytes);
2264 		}
2265 	}
2266 }
2267 /* }}} */
2268 
2269 /* {{{ Canonicalize nodes to a string */
PHP_METHOD(DOMNode,C14N)2270 PHP_METHOD(DOMNode, C14N)
2271 {
2272 	dom_canonicalization(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
2273 }
2274 /* }}} */
2275 
2276 /* {{{ Canonicalize nodes to a file */
PHP_METHOD(DOMNode,C14NFile)2277 PHP_METHOD(DOMNode, C14NFile)
2278 {
2279 	dom_canonicalization(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
2280 }
2281 /* }}} */
2282 
2283 /* {{{ Gets an xpath for a node */
dom_node_get_node_path(INTERNAL_FUNCTION_PARAMETERS,bool throw)2284 static void dom_node_get_node_path(INTERNAL_FUNCTION_PARAMETERS, bool throw)
2285 {
2286 	zval *id;
2287 	xmlNode *nodep;
2288 	dom_object *intern;
2289 	char *value;
2290 
2291 	if (zend_parse_parameters_none() == FAILURE) {
2292 		RETURN_THROWS();
2293 	}
2294 
2295 	DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
2296 
2297 	value = (char *) xmlGetNodePath(nodep);
2298 	if (value == NULL) {
2299 		/* This is only possible when an invalid argument is passed (e.g. namespace declaration, but that's not the case for this call site),
2300 		 * or on allocation failure. So in other words, this only happens on allocation failure. */
2301 		if (throw) {
2302 			php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
2303 			RETURN_THROWS();
2304 		}
2305 		RETURN_NULL();
2306 	} else {
2307 		RETVAL_STRING(value);
2308 		xmlFree(value);
2309 	}
2310 }
2311 
PHP_METHOD(DOMNode,getNodePath)2312 PHP_METHOD(DOMNode, getNodePath)
2313 {
2314 	dom_node_get_node_path(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
2315 }
2316 
PHP_METHOD(DOM_Node,getNodePath)2317 PHP_METHOD(DOM_Node, getNodePath)
2318 {
2319 	dom_node_get_node_path(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
2320 }
2321 /* }}} */
2322 
2323 /* {{{ Gets line number for a node */
PHP_METHOD(DOMNode,getLineNo)2324 PHP_METHOD(DOMNode, getLineNo)
2325 {
2326 	zval *id;
2327 	xmlNode *nodep;
2328 	dom_object *intern;
2329 
2330 	if (zend_parse_parameters_none() == FAILURE) {
2331 		RETURN_THROWS();
2332 	}
2333 
2334 	DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
2335 
2336 	RETURN_LONG(xmlGetLineNo(nodep));
2337 }
2338 /* }}} */
2339 
2340 /* {{{ URL: https://dom.spec.whatwg.org/#dom-node-contains
2341 Since:
2342 */
dom_node_contains(xmlNodePtr thisp,xmlNodePtr otherp)2343 static bool dom_node_contains(xmlNodePtr thisp, xmlNodePtr otherp)
2344 {
2345 	do {
2346 		if (otherp == thisp) {
2347 			return true;
2348 		}
2349 		otherp = otherp->parent;
2350 	} while (otherp);
2351 
2352 	return false;
2353 }
2354 
PHP_METHOD(DOMNode,contains)2355 PHP_METHOD(DOMNode, contains)
2356 {
2357 	zval *other, *id;
2358 	xmlNodePtr otherp, thisp;
2359 	dom_object *unused_intern;
2360 
2361 	ZEND_PARSE_PARAMETERS_START(1, 1)
2362 		Z_PARAM_OBJECT_OR_NULL(other)
2363 	ZEND_PARSE_PARAMETERS_END();
2364 
2365 	if (other == NULL) {
2366 		RETURN_FALSE;
2367 	}
2368 
2369 	if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(other), dom_node_class_entry) && !instanceof_function(Z_OBJCE_P(other), dom_namespace_node_class_entry))) {
2370 		zend_argument_type_error(1, "must be of type DOMNode|DOMNameSpaceNode|null, %s given", zend_zval_value_name(other));
2371 		RETURN_THROWS();
2372 	}
2373 
2374 	DOM_GET_OBJ(otherp, other, xmlNodePtr, unused_intern);
2375 	DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, unused_intern);
2376 
2377 	RETURN_BOOL(dom_node_contains(thisp, otherp));
2378 }
2379 
PHP_METHOD(DOM_Node,contains)2380 PHP_METHOD(DOM_Node, contains)
2381 {
2382 	zval *other, *id;
2383 	xmlNodePtr otherp, thisp;
2384 	dom_object *unused_intern;
2385 
2386 	ZEND_PARSE_PARAMETERS_START(1, 1)
2387 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(other, dom_modern_node_class_entry)
2388 	ZEND_PARSE_PARAMETERS_END();
2389 
2390 	if (other == NULL) {
2391 		RETURN_FALSE;
2392 	}
2393 
2394 	DOM_GET_OBJ(otherp, other, xmlNodePtr, unused_intern);
2395 	DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, unused_intern);
2396 
2397 	RETURN_BOOL(dom_node_contains(thisp, otherp));
2398 }
2399 /* }}} */
2400 
2401 /* {{{ URL: https://dom.spec.whatwg.org/#dom-node-getrootnode
2402 Since:
2403 */
PHP_METHOD(DOMNode,getRootNode)2404 PHP_METHOD(DOMNode, getRootNode)
2405 {
2406 	zval *id;
2407 	xmlNodePtr thisp;
2408 	dom_object *intern;
2409 	/* Unused now because we don't support the shadow DOM nodes. Options only influence shadow DOM nodes. */
2410 	zval *options;
2411 
2412 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a!", &options) != SUCCESS) {
2413 		RETURN_THROWS();
2414 	}
2415 
2416 	DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, intern);
2417 
2418 	while (thisp->parent) {
2419 		thisp = thisp->parent;
2420 	}
2421 
2422 	DOM_RET_OBJ(thisp, intern);
2423 }
2424 /* }}} */
2425 
2426 /* {{{ URL: https://dom.spec.whatwg.org/#dom-node-comparedocumentposition (last check date 2023-07-24)
2427 Since:
2428 */
2429 
2430 #define DOCUMENT_POSITION_DISCONNECTED 0x01
2431 #define DOCUMENT_POSITION_PRECEDING 0x02
2432 #define DOCUMENT_POSITION_FOLLOWING 0x04
2433 #define DOCUMENT_POSITION_CONTAINS 0x08
2434 #define DOCUMENT_POSITION_CONTAINED_BY 0x10
2435 #define DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC 0x20
2436 
dom_node_compare_document_position(INTERNAL_FUNCTION_PARAMETERS,zend_class_entry * node_ce)2437 static void dom_node_compare_document_position(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *node_ce)
2438 {
2439 	zval *id, *node_zval;
2440 	xmlNodePtr other, this;
2441 	dom_object *this_intern, *other_intern;
2442 
2443 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node_zval, node_ce) != SUCCESS) {
2444 		RETURN_THROWS();
2445 	}
2446 
2447 	DOM_GET_THIS_OBJ(this, id, xmlNodePtr, this_intern);
2448 	DOM_GET_OBJ(other, node_zval, xmlNodePtr, other_intern);
2449 
2450 	/* Step 1 */
2451 	if (this == other) {
2452 		RETURN_LONG(0);
2453 	}
2454 
2455 	/* Step 2 */
2456 	xmlNodePtr node1 = other;
2457 	xmlNodePtr node2 = this;
2458 
2459 	/* Step 3 */
2460 	xmlNodePtr attr1 = NULL;
2461 	xmlNodePtr attr2 = NULL;
2462 
2463 	/* Step 4 */
2464 	if (node1->type == XML_ATTRIBUTE_NODE) {
2465 		attr1 = node1;
2466 		node1 = attr1->parent;
2467 	}
2468 
2469 	/* Step 5 */
2470 	if (node2->type == XML_ATTRIBUTE_NODE) {
2471 		/* 5.1 */
2472 		attr2 = node2;
2473 		node2 = attr2->parent;
2474 
2475 		/* 5.2 */
2476 		if (attr1 != NULL && node1 != NULL && node2 == node1) {
2477 			for (const xmlAttr *attr = node2->properties; attr != NULL; attr = attr->next) {
2478 				if (php_dom_is_equal_attr(attr, (const xmlAttr *) attr1)) {
2479 					RETURN_LONG(DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | DOCUMENT_POSITION_PRECEDING);
2480 				} else if (php_dom_is_equal_attr(attr, (const xmlAttr *) attr2)) {
2481 					RETURN_LONG(DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | DOCUMENT_POSITION_FOLLOWING);
2482 				}
2483 			}
2484 		}
2485 	}
2486 
2487 	/* Step 6 */
2488 	/* We first check the first condition,
2489 	 * and as we need the root later anyway we'll cache the root and perform the root check after this if. */
2490 	if (node1 == NULL || node2 == NULL) {
2491 		goto disconnected;
2492 	}
2493 	bool node2_is_ancestor_of_node1 = false;
2494 	size_t node1_depth = 0;
2495 	xmlNodePtr node1_root = node1;
2496 	while (node1_root->parent) {
2497 		node1_root = node1_root->parent;
2498 		if (node1_root == node2) {
2499 			node2_is_ancestor_of_node1 = true;
2500 		}
2501 		node1_depth++;
2502 	}
2503 	bool node1_is_ancestor_of_node2 = false;
2504 	size_t node2_depth = 0;
2505 	xmlNodePtr node2_root = node2;
2506 	while (node2_root->parent) {
2507 		node2_root = node2_root->parent;
2508 		if (node2_root == node1) {
2509 			node1_is_ancestor_of_node2 = true;
2510 		}
2511 		node2_depth++;
2512 	}
2513 	/* Second condition from step 6 */
2514 	if (node1_root != node2_root) {
2515 		goto disconnected;
2516 	}
2517 
2518 	/* Step 7 */
2519 	if ((node1_is_ancestor_of_node2 && attr1 == NULL) || (node1 == node2 && attr2 != NULL)) {
2520 		RETURN_LONG(DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING);
2521 	}
2522 
2523 	/* Step 8 */
2524 	if ((node2_is_ancestor_of_node1 && attr2 == NULL) || (node1 == node2 && attr1 != NULL)) {
2525 		RETURN_LONG(DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING);
2526 	}
2527 
2528 	/* Special case: comparing children and attributes.
2529 	 * They belong to a different tree and are therefore hard to compare, but spec demands attributes to precede children
2530 	 * according to the pre-order depth-first search ordering.
2531 	 * Because their tree is different, the node parents only meet at the common element instead of earlier.
2532 	 * Therefore, it seems that one is the ancestor of the other. */
2533 	if (node1_is_ancestor_of_node2) {
2534 		ZEND_ASSERT(attr1 != NULL); /* Would've been handled in step 7 otherwise */
2535 		RETURN_LONG(DOCUMENT_POSITION_PRECEDING);
2536 	} else if (node2_is_ancestor_of_node1) {
2537 		ZEND_ASSERT(attr2 != NULL); /* Would've been handled in step 8 otherwise */
2538 		RETURN_LONG(DOCUMENT_POSITION_FOLLOWING);
2539 	}
2540 
2541 	/* Step 9 */
2542 
2543 	/* We'll use the following strategy (which was already prepared during step 6) to implement this efficiently:
2544 	 * 1. Move nodes upwards such that they are at the same depth.
2545 	 * 2. Then we move both nodes upwards simultaneously until their parents are equal.
2546 	 * 3. If we then move node1 to the next entry repeatedly and we encounter node2,
2547 	 *    then we know node1 precedes node2. Otherwise, node2 must precede node1. */
2548 	/* 1. */
2549 	if (node1_depth > node2_depth) {
2550 		do {
2551 			node1 = node1->parent;
2552 			node1_depth--;
2553 		} while (node1_depth > node2_depth);
2554 	} else if (node2_depth > node1_depth) {
2555 		do {
2556 			node2 = node2->parent;
2557 			node2_depth--;
2558 		} while (node2_depth > node1_depth);
2559 	}
2560 	/* 2. */
2561 	while (node1->parent != node2->parent) {
2562 		node1 = node1->parent;
2563 		node2 = node2->parent;
2564 	}
2565 	/* 3. */
2566 	ZEND_ASSERT(node1 != node2);
2567 	ZEND_ASSERT(node1 != NULL);
2568 	ZEND_ASSERT(node2 != NULL);
2569 	do {
2570 		node1 = node1->next;
2571 		if (node1 == node2) {
2572 			RETURN_LONG(DOCUMENT_POSITION_PRECEDING);
2573 		}
2574 	} while (node1 != NULL);
2575 
2576 	/* Step 10 */
2577 	RETURN_LONG(DOCUMENT_POSITION_FOLLOWING);
2578 
2579 disconnected:;
2580 	zend_long ordering;
2581 	if (node1 == node2) {
2582 		/* Degenerate case, they're both NULL, but the ordering must be consistent... */
2583 		ZEND_ASSERT(node1 == NULL);
2584 		ordering = other_intern < this_intern ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING;
2585 	} else {
2586 		ordering = node1 < node2 ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING;
2587 	}
2588 	RETURN_LONG(DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | ordering);
2589 }
2590 
PHP_METHOD(DOMNode,compareDocumentPosition)2591 PHP_METHOD(DOMNode, compareDocumentPosition)
2592 {
2593 	dom_node_compare_document_position(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_node_class_entry);
2594 }
2595 
PHP_METHOD(DOM_Node,compareDocumentPosition)2596 PHP_METHOD(DOM_Node, compareDocumentPosition)
2597 {
2598 	dom_node_compare_document_position(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_modern_node_class_entry);
2599 }
2600 /* }}} */
2601 
2602 /**
2603  * We want to block the serialization and unserialization of DOM classes.
2604  * However, using @not-serializable makes the child classes also not serializable, even if the user implements the methods.
2605  * So instead, we implement the methods wherein we throw exceptions.
2606  * The reason we choose these methods is because:
2607  *   - If the user implements __serialize / __unserialize, the respective throwing methods are not called.
2608  *   - If the user implements __sleep / __wakeup, then it's also not a problem because they will not enter the throwing methods.
2609  */
2610 
PHP_METHOD(DOM_Node,__construct)2611 PHP_METHOD(DOM_Node, __construct)
2612 {
2613 	ZEND_UNREACHABLE();
2614 }
2615 
PHP_METHOD(DOMNode,__sleep)2616 PHP_METHOD(DOMNode, __sleep)
2617 {
2618 	if (zend_parse_parameters_none() != SUCCESS) {
2619 		RETURN_THROWS();
2620 	}
2621 
2622 	zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed, unless serialization methods are implemented in a subclass", ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
2623 	RETURN_THROWS();
2624 }
2625 
PHP_METHOD(DOMNode,__wakeup)2626 PHP_METHOD(DOMNode, __wakeup)
2627 {
2628 	if (zend_parse_parameters_none() != SUCCESS) {
2629 		RETURN_THROWS();
2630 	}
2631 
2632 	zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed, unless unserialization methods are implemented in a subclass", ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
2633 	RETURN_THROWS();
2634 }
2635 
2636 #endif
2637