xref: /PHP-8.3/ext/dom/node.c (revision 9d8983c0)
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 int 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 int 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 int 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 int 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 int 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 int 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 int 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 int 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 int 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 int 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 int 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 int 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 int 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 int 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 int 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 int 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 int 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 int 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 int 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 (nsnode && nodep->ns != NULL && !xmlStrEqual(nodep->ns->prefix, (xmlChar *)prefix)) {
678 				strURI = (char *) nodep->ns->href;
679 				if (strURI == NULL ||
680 					(zend_string_equals_literal(prefix_str, "xml") && strcmp(strURI, (char *) XML_XML_NAMESPACE)) ||
681 					(nodep->type == XML_ATTRIBUTE_NODE && zend_string_equals_literal(prefix_str, "xmlns") &&
682 					 strcmp(strURI, (char *) DOM_XMLNS_NAMESPACE)) ||
683 					(nodep->type == XML_ATTRIBUTE_NODE && !strcmp((char *) nodep->name, "xmlns"))) {
684 					ns = NULL;
685 				} else {
686 					curns = nsnode->nsDef;
687 					while (curns != NULL) {
688 						if (xmlStrEqual((xmlChar *)prefix, curns->prefix) && xmlStrEqual(nodep->ns->href, curns->href)) {
689 							ns = curns;
690 							break;
691 						}
692 						curns = curns->next;
693 					}
694 					if (ns == NULL) {
695 						ns = xmlNewNs(nsnode, nodep->ns->href, (xmlChar *)prefix);
696 					}
697 				}
698 
699 				if (ns == NULL) {
700 					php_dom_throw_error(NAMESPACE_ERR, dom_get_strict_error(obj->document));
701 					return FAILURE;
702 				}
703 
704 				xmlSetNs(nodep, ns);
705 			}
706 			break;
707 		default:
708 			break;
709 	}
710 
711 	return SUCCESS;
712 }
713 
714 /* }}} */
715 
716 /* {{{ localName	string
717 readonly=yes
718 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-NodeNSLocalN
719 Since: DOM Level 2
720 */
dom_node_local_name_read(dom_object * obj,zval * retval)721 int dom_node_local_name_read(dom_object *obj, zval *retval)
722 {
723 	xmlNode *nodep = dom_object_get_node(obj);
724 
725 	if (nodep == NULL) {
726 		php_dom_throw_error(INVALID_STATE_ERR, 1);
727 		return FAILURE;
728 	}
729 
730 	if (nodep->type == XML_ELEMENT_NODE || nodep->type == XML_ATTRIBUTE_NODE || nodep->type == XML_NAMESPACE_DECL) {
731 		ZVAL_STRING(retval, (char *) (nodep->name));
732 	} else {
733 		ZVAL_NULL(retval);
734 	}
735 
736 	return SUCCESS;
737 }
738 
739 /* }}} */
740 
741 /* {{{ baseURI	string
742 readonly=yes
743 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Node3-baseURI
744 Since: DOM Level 3
745 */
dom_node_base_uri_read(dom_object * obj,zval * retval)746 int dom_node_base_uri_read(dom_object *obj, zval *retval)
747 {
748 	xmlNode *nodep = dom_object_get_node(obj);
749 	xmlChar *baseuri;
750 
751 	if (nodep == NULL) {
752 		php_dom_throw_error(INVALID_STATE_ERR, 1);
753 		return FAILURE;
754 	}
755 
756 	baseuri = xmlNodeGetBase(nodep->doc, nodep);
757 	if (baseuri) {
758 		ZVAL_STRING(retval, (char *) (baseuri));
759 		xmlFree(baseuri);
760 	} else {
761 		ZVAL_NULL(retval);
762 	}
763 
764 	return SUCCESS;
765 }
766 
767 /* }}} */
768 
769 /* {{{ textContent	string
770 readonly=no
771 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Node3-textContent
772 Since: DOM Level 3
773 */
dom_node_text_content_read(dom_object * obj,zval * retval)774 int dom_node_text_content_read(dom_object *obj, zval *retval)
775 {
776 	xmlNode *nodep = dom_object_get_node(obj);
777 
778 	if (nodep == NULL) {
779 		php_dom_throw_error(INVALID_STATE_ERR, 1);
780 		return FAILURE;
781 	}
782 
783 	php_dom_get_content_into_zval(nodep, retval, false);
784 
785 	return SUCCESS;
786 }
787 
dom_node_text_content_write(dom_object * obj,zval * newval)788 int dom_node_text_content_write(dom_object *obj, zval *newval)
789 {
790 	xmlNode *nodep = dom_object_get_node(obj);
791 
792 	if (nodep == NULL) {
793 		php_dom_throw_error(INVALID_STATE_ERR, 1);
794 		return FAILURE;
795 	}
796 
797 	php_libxml_invalidate_node_list_cache(obj->document);
798 
799 	/* Typed property, this is already a string */
800 	ZEND_ASSERT(Z_TYPE_P(newval) == IS_STRING);
801 	const xmlChar *xmlChars = (const xmlChar *) Z_STRVAL_P(newval);
802 	int type = nodep->type;
803 
804 	/* We can't directly call xmlNodeSetContent, because it might encode the string through
805 	 * xmlStringLenGetNodeList for types XML_DOCUMENT_FRAG_NODE, XML_ELEMENT_NODE, XML_ATTRIBUTE_NODE.
806 	 * See tree.c:xmlNodeSetContent in libxml.
807 	 * In these cases we need to use a text node to avoid the encoding.
808 	 * For the other cases, we *can* rely on xmlNodeSetContent because it is either a no-op, or handles
809 	 * the content without encoding. */
810 	if (type == XML_DOCUMENT_FRAG_NODE || type == XML_ELEMENT_NODE || type == XML_ATTRIBUTE_NODE) {
811 		dom_remove_all_children(nodep);
812 		xmlNode *textNode = xmlNewText(xmlChars);
813 		xmlAddChild(nodep, textNode);
814 	} else {
815 		xmlNodeSetContent(nodep, xmlChars);
816 	}
817 
818 	return SUCCESS;
819 }
820 
821 /* }}} */
822 
823 /* Returns true if the node had the same document reference, false otherwise. */
dom_set_document_ref_obj_single(xmlNodePtr node,php_libxml_ref_obj * document)824 static bool dom_set_document_ref_obj_single(xmlNodePtr node, php_libxml_ref_obj *document)
825 {
826 	dom_object *childobj = php_dom_object_get_data(node);
827 	if (!childobj) {
828 		return true;
829 	}
830 	if (!childobj->document) {
831 		childobj->document = document;
832 		document->refcount++;
833 		return true;
834 	}
835 	return false;
836 }
837 
dom_set_document_ref_pointers_attr(xmlAttrPtr attr,php_libxml_ref_obj * document)838 void dom_set_document_ref_pointers_attr(xmlAttrPtr attr, php_libxml_ref_obj *document)
839 {
840 	ZEND_ASSERT(document != NULL);
841 
842 	dom_set_document_ref_obj_single((xmlNodePtr) attr, document);
843 	for (xmlNodePtr attr_child = attr->children; attr_child; attr_child = attr_child->next) {
844 		dom_set_document_ref_obj_single(attr_child, document);
845 	}
846 }
847 
dom_set_document_ref_pointers_node(xmlNodePtr node,php_libxml_ref_obj * document)848 static bool dom_set_document_ref_pointers_node(xmlNodePtr node, php_libxml_ref_obj *document)
849 {
850 	ZEND_ASSERT(document != NULL);
851 
852 	if (!dom_set_document_ref_obj_single(node, document)) {
853 		return false;
854 	}
855 
856 	if (node->type == XML_ELEMENT_NODE) {
857 		for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
858 			dom_set_document_ref_pointers_attr(attr, document);
859 		}
860 	}
861 
862 	return true;
863 }
864 
865 /* TODO: on 8.4 replace the loop with the tree walk helper function. */
dom_set_document_ref_pointers(xmlNodePtr node,php_libxml_ref_obj * document)866 void dom_set_document_ref_pointers(xmlNodePtr node, php_libxml_ref_obj *document)
867 {
868 	if (!document) {
869 		return;
870 	}
871 
872 	if (!dom_set_document_ref_pointers_node(node, document)) {
873 		return;
874 	}
875 
876 	xmlNodePtr base = node;
877 	node = node->children;
878 	while (node != NULL) {
879 		ZEND_ASSERT(node != base);
880 
881 		if (!dom_set_document_ref_pointers_node(node, document)) {
882 			break;
883 		}
884 
885 		if (node->type == XML_ELEMENT_NODE) {
886 			if (node->children) {
887 				node = node->children;
888 				continue;
889 			}
890 		}
891 
892 		if (node->next) {
893 			node = node->next;
894 		} else {
895 			/* Go upwards, until we find a parent node with a next sibling, or until we hit the base. */
896 			do {
897 				node = node->parent;
898 				if (node == base) {
899 					return;
900 				}
901 			} while (node->next == NULL);
902 			node = node->next;
903 		}
904 	}
905 }
906 
_php_dom_insert_fragment(xmlNodePtr nodep,xmlNodePtr prevsib,xmlNodePtr nextsib,xmlNodePtr fragment,dom_object * intern,dom_object * childobj)907 static xmlNodePtr _php_dom_insert_fragment(xmlNodePtr nodep, xmlNodePtr prevsib, xmlNodePtr nextsib, xmlNodePtr fragment, dom_object *intern, dom_object *childobj) /* {{{ */
908 {
909 	xmlNodePtr newchild = fragment->children;
910 
911 	if (newchild) {
912 		if (prevsib == NULL) {
913 			nodep->children = newchild;
914 		} else {
915 			prevsib->next = newchild;
916 		}
917 		newchild->prev = prevsib;
918 		if (nextsib == NULL) {
919 			nodep->last = fragment->last;
920 		} else {
921 			fragment->last->next = nextsib;
922 			nextsib->prev = fragment->last;
923 		}
924 
925 		/* Assign parent node pointer */
926 		xmlNodePtr node = newchild;
927 		while (node != NULL) {
928 			node->parent = nodep;
929 			if (node == fragment->last) {
930 				break;
931 			}
932 			node = node->next;
933 		}
934 
935 		fragment->children = NULL;
936 		fragment->last = NULL;
937 	}
938 
939 	return newchild;
940 }
941 /* }}} */
942 
dom_node_check_legacy_insertion_validity(xmlNodePtr parentp,xmlNodePtr child,bool stricterror,bool warn_empty_fragment)943 static bool dom_node_check_legacy_insertion_validity(xmlNodePtr parentp, xmlNodePtr child, bool stricterror, bool warn_empty_fragment)
944 {
945 	if (dom_node_is_read_only(parentp) == SUCCESS ||
946 		(child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
947 		php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
948 		return false;
949 	}
950 
951 	if (dom_hierarchy(parentp, child) == FAILURE) {
952 		php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
953 		return false;
954 	}
955 
956 	if (child->doc != parentp->doc && child->doc != NULL) {
957 		php_dom_throw_error(WRONG_DOCUMENT_ERR, stricterror);
958 		return false;
959 	}
960 
961 	if (warn_empty_fragment && child->type == XML_DOCUMENT_FRAG_NODE && child->children == NULL) {
962 		/* TODO Drop Warning? */
963 		php_error_docref(NULL, E_WARNING, "Document Fragment is empty");
964 		return false;
965 	}
966 
967 	/* In old DOM only text nodes and entity nodes can be added as children to attributes. */
968 	if (parentp->type == XML_ATTRIBUTE_NODE && child->type != XML_TEXT_NODE && child->type != XML_ENTITY_REF_NODE) {
969 		php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
970 		return false;
971 	}
972 	/* Attributes must be in elements. */
973 	if (child->type == XML_ATTRIBUTE_NODE && parentp->type != XML_ELEMENT_NODE) {
974 		php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
975 		return false;
976 	}
977 
978 	/* Documents can never be a child. */
979 	if (child->type == XML_DOCUMENT_NODE || child->type == XML_HTML_DOCUMENT_NODE) {
980 		php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
981 		return false;
982 	}
983 
984 	return true;
985 }
986 
987 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-952280727
988 Since:
989 */
PHP_METHOD(DOMNode,insertBefore)990 PHP_METHOD(DOMNode, insertBefore)
991 {
992 	zval *id, *node, *ref = NULL;
993 	xmlNodePtr child, new_child, parentp, refp = NULL;
994 	dom_object *intern, *childobj, *refpobj;
995 	int ret, stricterror;
996 
997 	id = ZEND_THIS;
998 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|O!", &node, dom_node_class_entry, &ref, dom_node_class_entry) == FAILURE) {
999 		RETURN_THROWS();
1000 	}
1001 
1002 	DOM_GET_OBJ(parentp, id, xmlNodePtr, intern);
1003 
1004 	if (dom_node_children_valid(parentp) == FAILURE) {
1005 		RETURN_FALSE;
1006 	}
1007 
1008 	DOM_GET_OBJ(child, node, xmlNodePtr, childobj);
1009 
1010 	new_child = NULL;
1011 
1012 	stricterror = dom_get_strict_error(intern->document);
1013 
1014 	if (!dom_node_check_legacy_insertion_validity(parentp, child, stricterror, true)) {
1015 		RETURN_FALSE;
1016 	}
1017 
1018 	if (ref != NULL) {
1019 		DOM_GET_OBJ(refp, ref, xmlNodePtr, refpobj);
1020 		if (refp->parent != parentp) {
1021 			php_dom_throw_error(NOT_FOUND_ERR, stricterror);
1022 			RETURN_FALSE;
1023 		}
1024 	}
1025 
1026 	if (child->doc == NULL && parentp->doc != NULL) {
1027 		dom_set_document_ref_pointers(child, intern->document);
1028 	}
1029 
1030 	php_libxml_invalidate_node_list_cache(intern->document);
1031 
1032 	if (ref != NULL) {
1033 		if (child->parent != NULL) {
1034 			xmlUnlinkNode(child);
1035 		}
1036 
1037 		if (child->type == XML_TEXT_NODE && (refp->type == XML_TEXT_NODE ||
1038 			(refp->prev != NULL && refp->prev->type == XML_TEXT_NODE))) {
1039 			new_child = child;
1040 			new_child->parent = refp->parent;
1041 			new_child->next = refp;
1042 			new_child->prev = refp->prev;
1043 			refp->prev = new_child;
1044 			if (new_child->prev != NULL) {
1045 				new_child->prev->next = new_child;
1046 			}
1047 			if (new_child->parent != NULL) {
1048 				if (new_child->parent->children == refp) {
1049 					new_child->parent->children = new_child;
1050 				}
1051 			}
1052 
1053 		} else if (child->type == XML_ATTRIBUTE_NODE) {
1054 			xmlAttrPtr lastattr;
1055 
1056 			if (child->ns == NULL)
1057 				lastattr = xmlHasProp(refp->parent, child->name);
1058 			else
1059 				lastattr = xmlHasNsProp(refp->parent, child->name, child->ns->href);
1060 			if (lastattr != NULL && lastattr->type != XML_ATTRIBUTE_DECL) {
1061 				if (lastattr != (xmlAttrPtr) child) {
1062 					xmlUnlinkNode((xmlNodePtr) lastattr);
1063 					php_libxml_node_free_resource((xmlNodePtr) lastattr);
1064 				} else {
1065 					DOM_RET_OBJ(child, &ret, intern);
1066 					return;
1067 				}
1068 			}
1069 			new_child = xmlAddPrevSibling(refp, child);
1070 			if (UNEXPECTED(NULL == new_child)) {
1071 				goto cannot_add;
1072 			}
1073 		} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
1074 			xmlNodePtr last = child->last;
1075 			new_child = _php_dom_insert_fragment(parentp, refp->prev, refp, child, intern, childobj);
1076 			dom_reconcile_ns_list(parentp->doc, new_child, last);
1077 		} else {
1078 			new_child = xmlAddPrevSibling(refp, child);
1079 			if (UNEXPECTED(NULL == new_child)) {
1080 				goto cannot_add;
1081 			}
1082 			dom_reconcile_ns(parentp->doc, new_child);
1083 		}
1084 	} else {
1085 		if (child->parent != NULL){
1086 			xmlUnlinkNode(child);
1087 		}
1088 		if (child->type == XML_TEXT_NODE && parentp->last != NULL && parentp->last->type == XML_TEXT_NODE) {
1089 			child->parent = parentp;
1090 			new_child = child;
1091 			if (parentp->children == NULL) {
1092 				parentp->children = child;
1093 				parentp->last = child;
1094 			} else {
1095 				child = parentp->last;
1096 				child->next = new_child;
1097 				new_child->prev = child;
1098 				parentp->last = new_child;
1099 			}
1100 		} else 	if (child->type == XML_ATTRIBUTE_NODE) {
1101 			xmlAttrPtr lastattr;
1102 
1103 			if (child->ns == NULL)
1104 				lastattr = xmlHasProp(parentp, child->name);
1105 			else
1106 				lastattr = xmlHasNsProp(parentp, child->name, child->ns->href);
1107 			if (lastattr != NULL && lastattr->type != XML_ATTRIBUTE_DECL) {
1108 				if (lastattr != (xmlAttrPtr) child) {
1109 					xmlUnlinkNode((xmlNodePtr) lastattr);
1110 					php_libxml_node_free_resource((xmlNodePtr) lastattr);
1111 				} else {
1112 					DOM_RET_OBJ(child, &ret, intern);
1113 					return;
1114 				}
1115 			}
1116 			new_child = xmlAddChild(parentp, child);
1117 			if (UNEXPECTED(NULL == new_child)) {
1118 				goto cannot_add;
1119 			}
1120 		} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
1121 			xmlNodePtr last = child->last;
1122 			new_child = _php_dom_insert_fragment(parentp, parentp->last, NULL, child, intern, childobj);
1123 			dom_reconcile_ns_list(parentp->doc, new_child, last);
1124 		} else {
1125 			new_child = xmlAddChild(parentp, child);
1126 			if (UNEXPECTED(NULL == new_child)) {
1127 				goto cannot_add;
1128 			}
1129 			dom_reconcile_ns(parentp->doc, new_child);
1130 		}
1131 	}
1132 
1133 	DOM_RET_OBJ(new_child, &ret, intern);
1134 	return;
1135 cannot_add:
1136 	zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode");
1137 	RETURN_THROWS();
1138 }
1139 /* }}} end dom_node_insert_before */
1140 
1141 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-785887307
1142 Since:
1143 */
PHP_METHOD(DOMNode,replaceChild)1144 PHP_METHOD(DOMNode, replaceChild)
1145 {
1146 	zval *id, *newnode, *oldnode;
1147 	xmlNodePtr newchild, oldchild, nodep;
1148 	dom_object *intern, *newchildobj, *oldchildobj;
1149 	int stricterror;
1150 	bool replacedoctype = false;
1151 
1152 	int ret;
1153 
1154 	id = ZEND_THIS;
1155 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "OO", &newnode, dom_node_class_entry, &oldnode, dom_node_class_entry) == FAILURE) {
1156 		RETURN_THROWS();
1157 	}
1158 
1159 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1160 
1161 	if (dom_node_children_valid(nodep) == FAILURE) {
1162 		RETURN_FALSE;
1163 	}
1164 
1165 	DOM_GET_OBJ(newchild, newnode, xmlNodePtr, newchildobj);
1166 	DOM_GET_OBJ(oldchild, oldnode, xmlNodePtr, oldchildobj);
1167 
1168 	if (!nodep->children) {
1169 		RETURN_FALSE;
1170 	}
1171 
1172 	stricterror = dom_get_strict_error(intern->document);
1173 
1174 	if (!dom_node_check_legacy_insertion_validity(nodep, newchild, stricterror, false)) {
1175 		RETURN_FALSE;
1176 	}
1177 
1178 	/* This is already disallowed by libxml, but we should check it here to avoid
1179 	 * breaking assumptions and assertions. */
1180 	if ((oldchild->type == XML_ATTRIBUTE_NODE) != (newchild->type == XML_ATTRIBUTE_NODE)) {
1181 		php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
1182 		RETURN_FALSE;
1183 	}
1184 
1185 	if (oldchild->parent != nodep) {
1186 		php_dom_throw_error(NOT_FOUND_ERR, stricterror);
1187 		RETURN_FALSE;
1188 	}
1189 
1190 	if (newchild->doc == NULL && nodep->doc != NULL) {
1191 		dom_set_document_ref_pointers(newchild, intern->document);
1192 	}
1193 
1194 	if (newchild->type == XML_DOCUMENT_FRAG_NODE) {
1195 		xmlNodePtr prevsib, nextsib;
1196 		prevsib = oldchild->prev;
1197 		nextsib = oldchild->next;
1198 
1199 		xmlUnlinkNode(oldchild);
1200 
1201 		xmlNodePtr last = newchild->last;
1202 		newchild = _php_dom_insert_fragment(nodep, prevsib, nextsib, newchild, intern, newchildobj);
1203 		if (newchild) {
1204 			dom_reconcile_ns_list(nodep->doc, newchild, last);
1205 		}
1206 	} else if (oldchild != newchild) {
1207 		xmlDtdPtr intSubset = xmlGetIntSubset(nodep->doc);
1208 		replacedoctype = (intSubset == (xmlDtd *) oldchild);
1209 
1210 		xmlReplaceNode(oldchild, newchild);
1211 		dom_reconcile_ns(nodep->doc, newchild);
1212 
1213 		if (replacedoctype) {
1214 			nodep->doc->intSubset = (xmlDtd *) newchild;
1215 		}
1216 	}
1217 	php_libxml_invalidate_node_list_cache(intern->document);
1218 	DOM_RET_OBJ(oldchild, &ret, intern);
1219 }
1220 /* }}} end dom_node_replace_child */
1221 
1222 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1734834066
1223 Since:
1224 */
PHP_METHOD(DOMNode,removeChild)1225 PHP_METHOD(DOMNode, removeChild)
1226 {
1227 	zval *id, *node;
1228 	xmlNodePtr child, nodep;
1229 	dom_object *intern, *childobj;
1230 	int ret, stricterror;
1231 
1232 	id = ZEND_THIS;
1233 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node, dom_node_class_entry) == FAILURE) {
1234 		RETURN_THROWS();
1235 	}
1236 
1237 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1238 
1239 	if (dom_node_children_valid(nodep) == FAILURE) {
1240 		RETURN_FALSE;
1241 	}
1242 
1243 	DOM_GET_OBJ(child, node, xmlNodePtr, childobj);
1244 
1245 	stricterror = dom_get_strict_error(intern->document);
1246 
1247 	if (dom_node_is_read_only(nodep) == SUCCESS ||
1248 		(child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
1249 		php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
1250 		RETURN_FALSE;
1251 	}
1252 
1253 	if (!nodep->children || child->parent != nodep) {
1254 		php_dom_throw_error(NOT_FOUND_ERR, stricterror);
1255 		RETURN_FALSE;
1256 	}
1257 
1258 	xmlUnlinkNode(child);
1259 	php_libxml_invalidate_node_list_cache(intern->document);
1260 	DOM_RET_OBJ(child, &ret, intern);
1261 }
1262 /* }}} end dom_node_remove_child */
1263 
1264 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-184E7107
1265 Since:
1266 */
PHP_METHOD(DOMNode,appendChild)1267 PHP_METHOD(DOMNode, appendChild)
1268 {
1269 	zval *id, *node;
1270 	xmlNodePtr child, nodep, new_child = NULL;
1271 	dom_object *intern, *childobj;
1272 	int ret, stricterror;
1273 
1274 	id = ZEND_THIS;
1275 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node, dom_node_class_entry) == FAILURE) {
1276 		RETURN_THROWS();
1277 	}
1278 
1279 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1280 
1281 	if (dom_node_children_valid(nodep) == FAILURE) {
1282 		RETURN_FALSE;
1283 	}
1284 
1285 	DOM_GET_OBJ(child, node, xmlNodePtr, childobj);
1286 
1287 	stricterror = dom_get_strict_error(intern->document);
1288 
1289 	if (!dom_node_check_legacy_insertion_validity(nodep, child, stricterror, true)) {
1290 		RETURN_FALSE;
1291 	}
1292 
1293 	if (child->doc == NULL && nodep->doc != NULL) {
1294 		dom_set_document_ref_pointers(child, intern->document);
1295 	}
1296 
1297 	if (child->parent != NULL){
1298 		xmlUnlinkNode(child);
1299 	}
1300 
1301 	if (child->type == XML_TEXT_NODE && nodep->last != NULL && nodep->last->type == XML_TEXT_NODE) {
1302 		child->parent = nodep;
1303 		new_child = child;
1304 		if (nodep->children == NULL) {
1305 			nodep->children = child;
1306 			nodep->last = child;
1307 		} else {
1308 			child = nodep->last;
1309 			child->next = new_child;
1310 			new_child->prev = child;
1311 			nodep->last = new_child;
1312 		}
1313 	} else 	if (child->type == XML_ATTRIBUTE_NODE) {
1314 		xmlAttrPtr lastattr;
1315 
1316 		if (child->ns == NULL)
1317 			lastattr = xmlHasProp(nodep, child->name);
1318 		else
1319 			lastattr = xmlHasNsProp(nodep, child->name, child->ns->href);
1320 		if (lastattr != NULL && lastattr->type != XML_ATTRIBUTE_DECL) {
1321 			if (lastattr != (xmlAttrPtr) child) {
1322 				xmlUnlinkNode((xmlNodePtr) lastattr);
1323 				php_libxml_node_free_resource((xmlNodePtr) lastattr);
1324 			}
1325 		}
1326 		new_child = xmlAddChild(nodep, child);
1327 		if (UNEXPECTED(new_child == NULL)) {
1328 			goto cannot_add;
1329 		}
1330 	} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
1331 		xmlNodePtr last = child->last;
1332 		new_child = _php_dom_insert_fragment(nodep, nodep->last, NULL, child, intern, childobj);
1333 		dom_reconcile_ns_list(nodep->doc, new_child, last);
1334 	} else {
1335 		new_child = xmlAddChild(nodep, child);
1336 		if (UNEXPECTED(new_child == NULL)) {
1337 			goto cannot_add;
1338 		}
1339 		dom_reconcile_ns(nodep->doc, new_child);
1340 	}
1341 
1342 	php_libxml_invalidate_node_list_cache(intern->document);
1343 
1344 	DOM_RET_OBJ(new_child, &ret, intern);
1345 	return;
1346 cannot_add:
1347 	// TODO Convert to Error?
1348 	php_error_docref(NULL, E_WARNING, "Couldn't append node");
1349 	RETURN_FALSE;
1350 }
1351 /* }}} end dom_node_append_child */
1352 
1353 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-810594187
1354 Since:
1355 */
PHP_METHOD(DOMNode,hasChildNodes)1356 PHP_METHOD(DOMNode, hasChildNodes)
1357 {
1358 	zval *id;
1359 	xmlNode *nodep;
1360 	dom_object *intern;
1361 
1362 	id = ZEND_THIS;
1363 	if (zend_parse_parameters_none() == FAILURE) {
1364 		RETURN_THROWS();
1365 	}
1366 
1367 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1368 
1369 	if (dom_node_children_valid(nodep) == FAILURE) {
1370 		RETURN_FALSE;
1371 	}
1372 
1373 	if (nodep->children) {
1374 		RETURN_TRUE;
1375 	} else {
1376 		RETURN_FALSE;
1377 	}
1378 }
1379 /* }}} end dom_node_has_child_nodes */
1380 
1381 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-3A0ED0A4
1382 Since:
1383 */
PHP_METHOD(DOMNode,cloneNode)1384 PHP_METHOD(DOMNode, cloneNode)
1385 {
1386 	zval *id;
1387 	xmlNode *n, *node;
1388 	int ret;
1389 	dom_object *intern;
1390 	bool recursive = 0;
1391 
1392 	id = ZEND_THIS;
1393 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &recursive) == FAILURE) {
1394 		RETURN_THROWS();
1395 	}
1396 
1397 	DOM_GET_OBJ(n, id, xmlNodePtr, intern);
1398 
1399 	node = xmlDocCopyNode(n, n->doc, recursive);
1400 
1401 	if (!node) {
1402 		RETURN_FALSE;
1403 	}
1404 
1405 	/* When deep is false Element nodes still require the attributes
1406 	Following taken from libxml as xmlDocCopyNode doesn't do this */
1407 	if (n->type == XML_ELEMENT_NODE && recursive == 0) {
1408 		if (n->nsDef != NULL) {
1409 			node->nsDef = xmlCopyNamespaceList(n->nsDef);
1410 		}
1411 		if (n->ns != NULL) {
1412 			xmlNsPtr ns;
1413 			ns = xmlSearchNs(n->doc, node, n->ns->prefix);
1414 			if (ns == NULL) {
1415 				ns = xmlSearchNs(n->doc, n, n->ns->prefix);
1416 				if (ns != NULL) {
1417 					xmlNodePtr root = node;
1418 
1419 					while (root->parent != NULL) {
1420 						root = root->parent;
1421 					}
1422 					node->ns = xmlNewNs(root, ns->href, ns->prefix);
1423 				}
1424 			} else {
1425 				node->ns = ns;
1426 			}
1427 		}
1428 		if (n->properties != NULL) {
1429 			node->properties = xmlCopyPropList(node, n->properties);
1430 		}
1431 	}
1432 
1433 	/* If document cloned we want a new document proxy */
1434 	if (node->doc != n->doc) {
1435 		intern = NULL;
1436 	}
1437 
1438 	DOM_RET_OBJ(node, &ret, intern);
1439 }
1440 /* }}} end dom_node_clone_node */
1441 
1442 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-normalize
1443 Since:
1444 */
PHP_METHOD(DOMNode,normalize)1445 PHP_METHOD(DOMNode, normalize)
1446 {
1447 	zval *id;
1448 	xmlNode *nodep;
1449 	dom_object *intern;
1450 
1451 	id = ZEND_THIS;
1452 	if (zend_parse_parameters_none() == FAILURE) {
1453 		RETURN_THROWS();
1454 	}
1455 
1456 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1457 
1458 	php_libxml_invalidate_node_list_cache(intern->document);
1459 
1460 	dom_normalize(nodep);
1461 
1462 }
1463 /* }}} end dom_node_normalize */
1464 
1465 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Level-2-Core-Node-supports
1466 Since: DOM Level 2
1467 */
PHP_METHOD(DOMNode,isSupported)1468 PHP_METHOD(DOMNode, isSupported)
1469 {
1470 	zend_string *feature, *version;
1471 
1472 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &feature, &version) == FAILURE) {
1473 		RETURN_THROWS();
1474 	}
1475 
1476 	RETURN_BOOL(dom_has_feature(feature, version));
1477 }
1478 /* }}} end dom_node_is_supported */
1479 
1480 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-NodeHasAttrs
1481 Since: DOM Level 2
1482 */
PHP_METHOD(DOMNode,hasAttributes)1483 PHP_METHOD(DOMNode, hasAttributes)
1484 {
1485 	zval *id;
1486 	xmlNode *nodep;
1487 	dom_object *intern;
1488 
1489 	id = ZEND_THIS;
1490 	if (zend_parse_parameters_none() == FAILURE) {
1491 		RETURN_THROWS();
1492 	}
1493 
1494 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1495 
1496 	if (nodep->type != XML_ELEMENT_NODE)
1497 		RETURN_FALSE;
1498 
1499 	if (nodep->properties) {
1500 		RETURN_TRUE;
1501 	} else {
1502 		RETURN_FALSE;
1503 	}
1504 }
1505 /* }}} end dom_node_has_attributes */
1506 
1507 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Node3-isSameNode
1508 Since: DOM Level 3
1509 */
PHP_METHOD(DOMNode,isSameNode)1510 PHP_METHOD(DOMNode, isSameNode)
1511 {
1512 	zval *id, *node;
1513 	xmlNodePtr nodeotherp, nodep;
1514 	dom_object *intern, *nodeotherobj;
1515 
1516 	id = ZEND_THIS;
1517 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node, dom_node_class_entry) == FAILURE) {
1518 		RETURN_THROWS();
1519 	}
1520 
1521 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1522 
1523 	DOM_GET_OBJ(nodeotherp, node, xmlNodePtr, nodeotherobj);
1524 
1525 	if (nodep == nodeotherp) {
1526 		RETURN_TRUE;
1527 	} else {
1528 		RETURN_FALSE;
1529 	}
1530 }
1531 /* }}} end dom_node_is_same_node */
1532 
php_dom_node_is_content_equal(const xmlNode * this,const xmlNode * other)1533 static bool php_dom_node_is_content_equal(const xmlNode *this, const xmlNode *other)
1534 {
1535 	xmlChar *this_content = xmlNodeGetContent(this);
1536 	xmlChar *other_content = xmlNodeGetContent(other);
1537 	bool result = xmlStrEqual(this_content, other_content);
1538 	xmlFree(this_content);
1539 	xmlFree(other_content);
1540 	return result;
1541 }
1542 
php_dom_node_is_ns_uri_equal(const xmlNode * this,const xmlNode * other)1543 static bool php_dom_node_is_ns_uri_equal(const xmlNode *this, const xmlNode *other)
1544 {
1545 	const xmlChar *this_ns = this->ns ? this->ns->href : NULL;
1546 	const xmlChar *other_ns = other->ns ? other->ns->href : NULL;
1547 	return xmlStrEqual(this_ns, other_ns);
1548 }
1549 
php_dom_node_is_ns_prefix_equal(const xmlNode * this,const xmlNode * other)1550 static bool php_dom_node_is_ns_prefix_equal(const xmlNode *this, const xmlNode *other)
1551 {
1552 	const xmlChar *this_ns = this->ns ? this->ns->prefix : NULL;
1553 	const xmlChar *other_ns = other->ns ? other->ns->prefix : NULL;
1554 	return xmlStrEqual(this_ns, other_ns);
1555 }
1556 
1557 static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other);
1558 
1559 #define PHP_DOM_FUNC_CAT(prefix, suffix) prefix##_##suffix
1560 /* xmlNode and xmlNs have incompatible struct layouts, i.e. the next field is in a different offset */
1561 #define PHP_DOM_DEFINE_LIST_COUNTER_HELPER(type)																		\
1562 	static size_t PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(const type *node)								\
1563 	{																													\
1564 		size_t counter = 0;																								\
1565 		while (node) {																									\
1566 			counter++;																									\
1567 			node = node->next;																							\
1568 		}																												\
1569 		return counter;																									\
1570 	}
1571 #define PHP_DOM_DEFINE_LIST_EQUALITY_ORDERED_HELPER(type)																\
1572 	static bool PHP_DOM_FUNC_CAT(php_dom_node_list_equality_check_ordered, type)(const type *list1, const type *list2)	\
1573 	{																													\
1574 		size_t count = PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list1);										\
1575 		if (count != PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list2)) {										\
1576 			return false;																								\
1577 		}																												\
1578 		for (size_t i = 0; i < count; i++) {																			\
1579 			if (!php_dom_node_is_equal_node((const xmlNode *) list1, (const xmlNode *) list2)) {						\
1580 				return false;																							\
1581 			}																											\
1582 			list1 = list1->next;																						\
1583 			list2 = list2->next;																						\
1584 		}																												\
1585 		return true;																									\
1586 	}
1587 #define PHP_DOM_DEFINE_LIST_EQUALITY_UNORDERED_HELPER(type)																\
1588 	static bool PHP_DOM_FUNC_CAT(php_dom_node_list_equality_check_unordered, type)(const type *list1, const type *list2)\
1589 	{																													\
1590 		size_t count = PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list1);										\
1591 		if (count != PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list2)) {										\
1592 			return false;																								\
1593 		}																												\
1594 		for (const type *n1 = list1; n1 != NULL; n1 = n1->next) {														\
1595 			bool found = false;																							\
1596 			for (const type *n2 = list2; n2 != NULL && !found; n2 = n2->next) {											\
1597 				if (php_dom_node_is_equal_node((const xmlNode *) n1, (const xmlNode *) n2)) {							\
1598 					found = true;																						\
1599 				}																										\
1600 			}																											\
1601 			if (!found) {																								\
1602 				return false;																							\
1603 			}																											\
1604 		}																												\
1605 		return true;																									\
1606 	}
1607 
1608 PHP_DOM_DEFINE_LIST_COUNTER_HELPER(xmlNode)
PHP_DOM_DEFINE_LIST_COUNTER_HELPER(xmlNs)1609 PHP_DOM_DEFINE_LIST_COUNTER_HELPER(xmlNs)
1610 PHP_DOM_DEFINE_LIST_EQUALITY_ORDERED_HELPER(xmlNode)
1611 PHP_DOM_DEFINE_LIST_EQUALITY_UNORDERED_HELPER(xmlNode)
1612 PHP_DOM_DEFINE_LIST_EQUALITY_UNORDERED_HELPER(xmlNs)
1613 
1614 static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other)
1615 {
1616 	ZEND_ASSERT(this != NULL);
1617 	ZEND_ASSERT(other != NULL);
1618 
1619 	if (this->type != other->type) {
1620 		return false;
1621 	}
1622 
1623 	/* Notes:
1624 	 *   - XML_DOCUMENT_TYPE_NODE is no longer created by libxml2, we only have to support XML_DTD_NODE.
1625 	 *   - element and attribute declarations are not exposed as nodes in DOM, so no comparison is needed for those. */
1626 	if (this->type == XML_ELEMENT_NODE) {
1627 		return xmlStrEqual(this->name, other->name)
1628 			&& php_dom_node_is_ns_prefix_equal(this, other)
1629 			&& php_dom_node_is_ns_uri_equal(this, other)
1630 			/* Check attributes first, then namespace declarations, then children */
1631 			&& php_dom_node_list_equality_check_unordered_xmlNode((const xmlNode *) this->properties, (const xmlNode *) other->properties)
1632 			&& php_dom_node_list_equality_check_unordered_xmlNs(this->nsDef, other->nsDef)
1633 			&& php_dom_node_list_equality_check_ordered_xmlNode(this->children, other->children);
1634 	} else if (this->type == XML_DTD_NODE) {
1635 		/* Note: in the living spec entity declarations and notations are no longer compared because they're considered obsolete. */
1636 		const xmlDtd *this_dtd = (const xmlDtd *) this;
1637 		const xmlDtd *other_dtd = (const xmlDtd *) other;
1638 		return xmlStrEqual(this_dtd->name, other_dtd->name)
1639 			&& xmlStrEqual(this_dtd->ExternalID, other_dtd->ExternalID)
1640 			&& xmlStrEqual(this_dtd->SystemID, other_dtd->SystemID);
1641 	} else if (this->type == XML_PI_NODE) {
1642 		return xmlStrEqual(this->name, other->name) && xmlStrEqual(this->content, other->content);
1643 	} else if (this->type == XML_TEXT_NODE || this->type == XML_COMMENT_NODE || this->type == XML_CDATA_SECTION_NODE) {
1644 		return xmlStrEqual(this->content, other->content);
1645 	} else if (this->type == XML_ATTRIBUTE_NODE) {
1646 		const xmlAttr *this_attr = (const xmlAttr *) this;
1647 		const xmlAttr *other_attr = (const xmlAttr *) other;
1648 		return xmlStrEqual(this_attr->name, other_attr->name)
1649 			&& php_dom_node_is_ns_uri_equal(this, other)
1650 			&& php_dom_node_is_content_equal(this, other);
1651 	} else if (this->type == XML_ENTITY_REF_NODE) {
1652 		return xmlStrEqual(this->name, other->name);
1653 	} else if (this->type == XML_ENTITY_DECL || this->type == XML_NOTATION_NODE || this->type == XML_ENTITY_NODE) {
1654 		const xmlEntity *this_entity = (const xmlEntity *) this;
1655 		const xmlEntity *other_entity = (const xmlEntity *) other;
1656 		return this_entity->etype == other_entity->etype
1657 			&& xmlStrEqual(this_entity->name, other_entity->name)
1658 			&& xmlStrEqual(this_entity->ExternalID, other_entity->ExternalID)
1659 			&& xmlStrEqual(this_entity->SystemID, other_entity->SystemID)
1660 			&& php_dom_node_is_content_equal(this, other);
1661 	} else if (this->type == XML_NAMESPACE_DECL) {
1662 		const xmlNs *this_ns = (const xmlNs *) this;
1663 		const xmlNs *other_ns = (const xmlNs *) other;
1664 		return xmlStrEqual(this_ns->prefix, other_ns->prefix) && xmlStrEqual(this_ns->href, other_ns->href);
1665 	} else if (this->type == XML_DOCUMENT_FRAG_NODE || this->type == XML_HTML_DOCUMENT_NODE || this->type == XML_DOCUMENT_NODE) {
1666 		return php_dom_node_list_equality_check_ordered_xmlNode(this->children, other->children);
1667 	}
1668 
1669 	return false;
1670 }
1671 
1672 /* {{{ URL: https://dom.spec.whatwg.org/#dom-node-isequalnode (for everything still in the living spec)
1673 *      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)
1674 Since: DOM Level 3
1675 */
PHP_METHOD(DOMNode,isEqualNode)1676 PHP_METHOD(DOMNode, isEqualNode)
1677 {
1678 	zval *id, *node;
1679 	xmlNodePtr otherp, nodep;
1680 	dom_object *unused_intern;
1681 
1682 	id = ZEND_THIS;
1683 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O!", &node, dom_node_class_entry) == FAILURE) {
1684 		RETURN_THROWS();
1685 	}
1686 
1687 	if (node == NULL) {
1688 		RETURN_FALSE;
1689 	}
1690 
1691 	DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, unused_intern);
1692 	DOM_GET_OBJ(otherp, node, xmlNodePtr, unused_intern);
1693 
1694 	if (nodep == otherp) {
1695 		RETURN_TRUE;
1696 	}
1697 
1698 	/* Empty fragments/documents only match if they're both empty */
1699 	if (UNEXPECTED(nodep == NULL || otherp == NULL)) {
1700 		RETURN_BOOL(nodep == NULL && otherp == NULL);
1701 	}
1702 
1703 	RETURN_BOOL(php_dom_node_is_equal_node(nodep, otherp));
1704 }
1705 /* }}} end DOMNode::isEqualNode */
1706 
1707 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Node3-lookupNamespacePrefix
1708 Since: DOM Level 3
1709 */
PHP_METHOD(DOMNode,lookupPrefix)1710 PHP_METHOD(DOMNode, lookupPrefix)
1711 {
1712 	zval *id;
1713 	xmlNodePtr nodep, lookupp = NULL;
1714 	dom_object *intern;
1715 	xmlNsPtr nsptr;
1716 	size_t uri_len = 0;
1717 	char *uri;
1718 
1719 	id = ZEND_THIS;
1720 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &uri, &uri_len) == FAILURE) {
1721 		RETURN_THROWS();
1722 	}
1723 
1724 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1725 
1726 	if (uri_len > 0) {
1727 		switch (nodep->type) {
1728 			case XML_ELEMENT_NODE:
1729 				lookupp = nodep;
1730 				break;
1731 			case XML_DOCUMENT_NODE:
1732 			case XML_HTML_DOCUMENT_NODE:
1733 				lookupp = xmlDocGetRootElement((xmlDocPtr) nodep);
1734 				break;
1735 			case XML_ENTITY_NODE :
1736 			case XML_NOTATION_NODE:
1737 			case XML_DOCUMENT_FRAG_NODE:
1738 			case XML_DOCUMENT_TYPE_NODE:
1739 			case XML_DTD_NODE:
1740 				RETURN_NULL();
1741 				break;
1742 			default:
1743 				lookupp =  nodep->parent;
1744 		}
1745 
1746 		if (lookupp != NULL) {
1747 			nsptr = xmlSearchNsByHref(lookupp->doc, lookupp, (xmlChar *) uri);
1748 			if (nsptr && nsptr->prefix != NULL) {
1749 				RETURN_STRING((char *) nsptr->prefix);
1750 			}
1751 		}
1752 	}
1753 
1754 	RETURN_NULL();
1755 }
1756 /* }}} end dom_node_lookup_prefix */
1757 
1758 /* {{{ URL: http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-isDefaultNamespace
1759 Since: DOM Level 3
1760 */
PHP_METHOD(DOMNode,isDefaultNamespace)1761 PHP_METHOD(DOMNode, isDefaultNamespace)
1762 {
1763 	zval *id;
1764 	xmlNodePtr nodep;
1765 	dom_object *intern;
1766 	xmlNsPtr nsptr;
1767 	size_t uri_len = 0;
1768 	char *uri;
1769 
1770 	id = ZEND_THIS;
1771 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &uri, &uri_len) == FAILURE) {
1772 		RETURN_THROWS();
1773 	}
1774 
1775 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1776 	if (nodep->type == XML_DOCUMENT_NODE || nodep->type == XML_HTML_DOCUMENT_NODE) {
1777 		nodep = xmlDocGetRootElement((xmlDocPtr) nodep);
1778 	}
1779 
1780 	if (nodep && uri_len > 0) {
1781 		nsptr = xmlSearchNs(nodep->doc, nodep, NULL);
1782 		if (nsptr && xmlStrEqual(nsptr->href, (xmlChar *) uri)) {
1783 			RETURN_TRUE;
1784 		}
1785 	}
1786 
1787 	RETURN_FALSE;
1788 }
1789 /* }}} end dom_node_is_default_namespace */
1790 
1791 /* {{{ URL: http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI
1792 Since: DOM Level 3
1793 */
PHP_METHOD(DOMNode,lookupNamespaceURI)1794 PHP_METHOD(DOMNode, lookupNamespaceURI)
1795 {
1796 	zval *id;
1797 	xmlNodePtr nodep;
1798 	dom_object *intern;
1799 	xmlNsPtr nsptr;
1800 	size_t prefix_len;
1801 	char *prefix;
1802 
1803 	id = ZEND_THIS;
1804 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!", &prefix, &prefix_len) == FAILURE) {
1805 		RETURN_THROWS();
1806 	}
1807 
1808 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1809 	if (nodep->type == XML_DOCUMENT_NODE || nodep->type == XML_HTML_DOCUMENT_NODE) {
1810 		nodep = xmlDocGetRootElement((xmlDocPtr) nodep);
1811 		if (nodep == NULL) {
1812 			RETURN_NULL();
1813 		}
1814 	}
1815 
1816 	nsptr = xmlSearchNs(nodep->doc, nodep, (xmlChar *) prefix);
1817 	if (nsptr && nsptr->href != NULL) {
1818 		RETURN_STRING((char *) nsptr->href);
1819 	}
1820 
1821 	RETURN_NULL();
1822 }
1823 /* }}} end dom_node_lookup_namespace_uri */
1824 
dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS,int mode)1825 static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */
1826 {
1827 	zval *id;
1828 	zval *xpath_array=NULL, *ns_prefixes=NULL;
1829 	xmlNodePtr nodep;
1830 	xmlDocPtr docp;
1831 	xmlNodeSetPtr nodeset = NULL;
1832 	dom_object *intern;
1833 	bool exclusive=0, with_comments=0;
1834 	xmlChar **inclusive_ns_prefixes = NULL;
1835 	char *file = NULL;
1836 	int ret = -1;
1837 	size_t file_len = 0;
1838 	xmlOutputBufferPtr buf;
1839 	xmlXPathContextPtr ctxp=NULL;
1840 	xmlXPathObjectPtr xpathobjp=NULL;
1841 
1842 	id = ZEND_THIS;
1843 	if (mode == 0) {
1844 		if (zend_parse_parameters(ZEND_NUM_ARGS(),
1845 			"|bba!a!", &exclusive, &with_comments,
1846 			&xpath_array, &ns_prefixes) == FAILURE) {
1847 			RETURN_THROWS();
1848 		}
1849 	} else {
1850 		if (zend_parse_parameters(ZEND_NUM_ARGS(),
1851 			"s|bba!a!", &file, &file_len, &exclusive,
1852 			&with_comments, &xpath_array, &ns_prefixes) == FAILURE) {
1853 			RETURN_THROWS();
1854 		}
1855 	}
1856 
1857 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1858 
1859 	docp = nodep->doc;
1860 
1861 	if (! docp) {
1862 		zend_throw_error(NULL, "Node must be associated with a document");
1863 		RETURN_THROWS();
1864 	}
1865 
1866 	php_libxml_invalidate_node_list_cache_from_doc(docp);
1867 
1868 	if (xpath_array == NULL) {
1869 		if (nodep->type != XML_DOCUMENT_NODE) {
1870 			ctxp = xmlXPathNewContext(docp);
1871 			ctxp->node = nodep;
1872 			xpathobjp = xmlXPathEvalExpression((xmlChar *) "(.//. | .//@* | .//namespace::*)", ctxp);
1873 			ctxp->node = NULL;
1874 			if (xpathobjp && xpathobjp->type == XPATH_NODESET) {
1875 				nodeset = xpathobjp->nodesetval;
1876 			} else {
1877 				if (xpathobjp) {
1878 					xmlXPathFreeObject(xpathobjp);
1879 				}
1880 				xmlXPathFreeContext(ctxp);
1881 				zend_throw_error(NULL, "XPath query did not return a nodeset");
1882 				RETURN_THROWS();
1883 			}
1884 		}
1885 	} else {
1886 		/*xpath query from xpath_array */
1887 		HashTable *ht = Z_ARRVAL_P(xpath_array);
1888 		zval *tmp;
1889 		char *xquery;
1890 
1891 		/* Find "query" key */
1892 		tmp = zend_hash_find_deref(ht, ZSTR_KNOWN(ZEND_STR_QUERY));
1893 		if (!tmp) {
1894 			/* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */
1895 			zend_argument_value_error(3 + mode, "must have a \"query\" key");
1896 			RETURN_THROWS();
1897 		}
1898 		if (Z_TYPE_P(tmp) != IS_STRING) {
1899 			/* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */
1900 			zend_argument_type_error(3 + mode, "\"query\" option must be a string, %s given", zend_zval_value_name(tmp));
1901 			RETURN_THROWS();
1902 		}
1903 		xquery = Z_STRVAL_P(tmp);
1904 
1905 		ctxp = xmlXPathNewContext(docp);
1906 		ctxp->node = nodep;
1907 
1908 		tmp = zend_hash_str_find_deref(ht, "namespaces", sizeof("namespaces")-1);
1909 		if (tmp && Z_TYPE_P(tmp) == IS_ARRAY && !HT_IS_PACKED(Z_ARRVAL_P(tmp))) {
1910 			zval *tmpns;
1911 			zend_string *prefix;
1912 
1913 			ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(tmp), prefix, tmpns) {
1914 				ZVAL_DEREF(tmpns);
1915 				if (Z_TYPE_P(tmpns) == IS_STRING) {
1916 					if (prefix) {
1917 						xmlXPathRegisterNs(ctxp, (xmlChar *) ZSTR_VAL(prefix), (xmlChar *) Z_STRVAL_P(tmpns));
1918 					}
1919 				}
1920 			} ZEND_HASH_FOREACH_END();
1921 		}
1922 
1923 		xpathobjp = xmlXPathEvalExpression((xmlChar *) xquery, ctxp);
1924 		ctxp->node = NULL;
1925 		if (xpathobjp && xpathobjp->type == XPATH_NODESET) {
1926 			nodeset = xpathobjp->nodesetval;
1927 		} else {
1928 			if (xpathobjp) {
1929 				xmlXPathFreeObject(xpathobjp);
1930 			}
1931 			xmlXPathFreeContext(ctxp);
1932 			zend_throw_error(NULL, "XPath query did not return a nodeset");
1933 			RETURN_THROWS();
1934 		}
1935 	}
1936 
1937 	if (ns_prefixes != NULL) {
1938 		if (exclusive) {
1939 			zval *tmpns;
1940 			int nscount = 0;
1941 
1942 			inclusive_ns_prefixes = safe_emalloc(zend_hash_num_elements(Z_ARRVAL_P(ns_prefixes)) + 1,
1943 				sizeof(xmlChar *), 0);
1944 			ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(ns_prefixes), tmpns) {
1945 				ZVAL_DEREF(tmpns);
1946 				if (Z_TYPE_P(tmpns) == IS_STRING) {
1947 					inclusive_ns_prefixes[nscount++] = (xmlChar *) Z_STRVAL_P(tmpns);
1948 				}
1949 			} ZEND_HASH_FOREACH_END();
1950 			inclusive_ns_prefixes[nscount] = NULL;
1951 		} else {
1952 			php_error_docref(NULL, E_NOTICE,
1953 				"Inclusive namespace prefixes only allowed in exclusive mode.");
1954 		}
1955 	}
1956 
1957 	if (mode == 1) {
1958 		buf = xmlOutputBufferCreateFilename(file, NULL, 0);
1959 	} else {
1960 		buf = xmlAllocOutputBuffer(NULL);
1961 	}
1962 
1963 	if (buf != NULL) {
1964 		ret = xmlC14NDocSaveTo(docp, nodeset, exclusive, inclusive_ns_prefixes,
1965 			with_comments, buf);
1966 	}
1967 
1968 	if (inclusive_ns_prefixes != NULL) {
1969 		efree(inclusive_ns_prefixes);
1970 	}
1971 	if (xpathobjp != NULL) {
1972 		xmlXPathFreeObject(xpathobjp);
1973 	}
1974 	if (ctxp != NULL) {
1975 		xmlXPathFreeContext(ctxp);
1976 	}
1977 
1978 	if (buf == NULL || ret < 0) {
1979 		RETVAL_FALSE;
1980 	} else {
1981 		if (mode == 0) {
1982 #ifdef LIBXML2_NEW_BUFFER
1983 			ret = xmlOutputBufferGetSize(buf);
1984 #else
1985 			ret = buf->buffer->use;
1986 #endif
1987 			if (ret > 0) {
1988 #ifdef LIBXML2_NEW_BUFFER
1989 				RETVAL_STRINGL((char *) xmlOutputBufferGetContent(buf), ret);
1990 #else
1991 				RETVAL_STRINGL((char *) buf->buffer->content, ret);
1992 #endif
1993 			} else {
1994 				RETVAL_EMPTY_STRING();
1995 			}
1996 		}
1997 	}
1998 
1999 	if (buf) {
2000 		int bytes;
2001 
2002 		bytes = xmlOutputBufferClose(buf);
2003 		if (mode == 1 && (ret >= 0)) {
2004 			RETURN_LONG(bytes);
2005 		}
2006 	}
2007 }
2008 /* }}} */
2009 
2010 /* {{{ Canonicalize nodes to a string */
PHP_METHOD(DOMNode,C14N)2011 PHP_METHOD(DOMNode, C14N)
2012 {
2013 	dom_canonicalization(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
2014 }
2015 /* }}} */
2016 
2017 /* {{{ Canonicalize nodes to a file */
PHP_METHOD(DOMNode,C14NFile)2018 PHP_METHOD(DOMNode, C14NFile)
2019 {
2020 	dom_canonicalization(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
2021 }
2022 /* }}} */
2023 
2024 /* {{{ Gets an xpath for a node */
PHP_METHOD(DOMNode,getNodePath)2025 PHP_METHOD(DOMNode, getNodePath)
2026 {
2027 	zval *id;
2028 	xmlNode *nodep;
2029 	dom_object *intern;
2030 	char *value;
2031 
2032 	if (zend_parse_parameters_none() == FAILURE) {
2033 		RETURN_THROWS();
2034 	}
2035 
2036 	DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
2037 
2038 	value = (char *) xmlGetNodePath(nodep);
2039 	if (value == NULL) {
2040 		/* TODO Research if can return empty string */
2041 		RETURN_NULL();
2042 	} else {
2043 		RETVAL_STRING(value);
2044 		xmlFree(value);
2045 	}
2046 }
2047 /* }}} */
2048 
2049 /* {{{ Gets line number for a node */
PHP_METHOD(DOMNode,getLineNo)2050 PHP_METHOD(DOMNode, getLineNo)
2051 {
2052 	zval *id;
2053 	xmlNode *nodep;
2054 	dom_object *intern;
2055 
2056 	if (zend_parse_parameters_none() == FAILURE) {
2057 		RETURN_THROWS();
2058 	}
2059 
2060 	DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
2061 
2062 	RETURN_LONG(xmlGetLineNo(nodep));
2063 }
2064 /* }}} */
2065 
2066 /* {{{ URL: https://dom.spec.whatwg.org/#dom-node-contains
2067 Since:
2068 */
PHP_METHOD(DOMNode,contains)2069 PHP_METHOD(DOMNode, contains)
2070 {
2071 	zval *other, *id;
2072 	xmlNodePtr otherp, thisp;
2073 	dom_object *unused_intern;
2074 
2075 	ZEND_PARSE_PARAMETERS_START(1, 1)
2076 		Z_PARAM_OBJECT_OR_NULL(other)
2077 	ZEND_PARSE_PARAMETERS_END();
2078 
2079 	if (other == NULL) {
2080 		RETURN_FALSE;
2081 	}
2082 
2083 	if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(other), dom_node_class_entry) && !instanceof_function(Z_OBJCE_P(other), dom_namespace_node_class_entry))) {
2084 		zend_argument_type_error(1, "must be of type DOMNode|DOMNameSpaceNode|null, %s given", zend_zval_value_name(other));
2085 		RETURN_THROWS();
2086 	}
2087 
2088 	DOM_GET_OBJ(otherp, other, xmlNodePtr, unused_intern);
2089 	DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, unused_intern);
2090 
2091 	do {
2092 		if (otherp == thisp) {
2093 			RETURN_TRUE;
2094 		}
2095 		otherp = otherp->parent;
2096 	} while (otherp);
2097 
2098 	RETURN_FALSE;
2099 }
2100 /* }}} */
2101 
2102 /* {{{ URL: https://dom.spec.whatwg.org/#dom-node-getrootnode
2103 Since:
2104 */
PHP_METHOD(DOMNode,getRootNode)2105 PHP_METHOD(DOMNode, getRootNode)
2106 {
2107 	zval *id;
2108 	xmlNodePtr thisp;
2109 	dom_object *intern;
2110 	/* Unused now because we don't support the shadow DOM nodes. Options only influence shadow DOM nodes. */
2111 	zval *options = NULL;
2112 
2113 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a!", &options) == FAILURE) {
2114 		RETURN_THROWS();
2115 	}
2116 
2117 	DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, intern);
2118 
2119 	while (thisp->parent) {
2120 		thisp = thisp->parent;
2121 	}
2122 
2123 	int ret;
2124 	DOM_RET_OBJ(thisp, &ret, intern);
2125 }
2126 /* }}} */
2127 
2128 /**
2129  * We want to block the serialization and unserialization of DOM classes.
2130  * However, using @not-serializable makes the child classes also not serializable, even if the user implements the methods.
2131  * So instead, we implement the methods wherein we throw exceptions.
2132  * The reason we choose these methods is because:
2133  *   - If the user implements __serialize / __unserialize, the respective throwing methods are not called.
2134  *   - If the user implements __sleep / __wakeup, then it's also not a problem because they will not enter the throwing methods.
2135  */
2136 
PHP_METHOD(DOMNode,__sleep)2137 PHP_METHOD(DOMNode, __sleep)
2138 {
2139 	if (zend_parse_parameters_none() != SUCCESS) {
2140 		RETURN_THROWS();
2141 	}
2142 
2143 	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));
2144 	RETURN_THROWS();
2145 }
2146 
PHP_METHOD(DOMNode,__wakeup)2147 PHP_METHOD(DOMNode, __wakeup)
2148 {
2149 	if (zend_parse_parameters_none() != SUCCESS) {
2150 		RETURN_THROWS();
2151 	}
2152 
2153 	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));
2154 	RETURN_THROWS();
2155 }
2156 
2157 #endif
2158