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