xref: /php-src/ext/dom/element.c (revision 55aa5f3f)
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 "zend_enum.h"
25 #include "php_dom.h"
26 #include "namespace_compat.h"
27 #include "private_data.h"
28 #include "internal_helpers.h"
29 #include "dom_properties.h"
30 #include "token_list.h"
31 
32 /*
33 * class DOMElement extends DOMNode
34 *
35 * URL: https://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-745549614
36 * Since:
37 */
38 
39 /* {{{ */
PHP_METHOD(DOMElement,__construct)40 PHP_METHOD(DOMElement, __construct)
41 {
42 	xmlNodePtr nodep = NULL, oldnode = NULL;
43 	dom_object *intern;
44 	char *name, *value = NULL, *uri = NULL;
45 	char *localname = NULL, *prefix = NULL;
46 	int errorcode = 0;
47 	size_t name_len, value_len = 0, uri_len = 0;
48 	int name_valid;
49 	xmlNsPtr nsptr = NULL;
50 
51 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s!s", &name, &name_len, &value, &value_len, &uri, &uri_len) == FAILURE) {
52 		RETURN_THROWS();
53 	}
54 
55 	name_valid = xmlValidateName(BAD_CAST name, 0);
56 	if (name_valid != 0) {
57 		php_dom_throw_error(INVALID_CHARACTER_ERR, true);
58 		RETURN_THROWS();
59 	}
60 
61 	/* Namespace logic is separate and only when uri passed in to insure no BC breakage */
62 	if (uri_len > 0) {
63 		errorcode = dom_check_qname(name, &localname, &prefix, uri_len, name_len);
64 		if (errorcode == 0) {
65 			nodep = xmlNewNode (NULL, BAD_CAST localname);
66 			if (nodep != NULL && uri != NULL) {
67 				nsptr = dom_get_ns(nodep, uri, &errorcode, prefix);
68 				xmlSetNs(nodep, nsptr);
69 			}
70 		}
71 		xmlFree(localname);
72 		if (prefix != NULL) {
73 			xmlFree(prefix);
74 		}
75 		if (errorcode != 0) {
76 			if (nodep != NULL) {
77 				xmlFreeNode(nodep);
78 			}
79 			php_dom_throw_error(errorcode, true);
80 			RETURN_THROWS();
81 		}
82 	} else {
83 	    /* If you don't pass a namespace uri, then you can't set a prefix */
84 	    localname = (char *) xmlSplitQName2(BAD_CAST name, (xmlChar **) &prefix);
85 	    if (prefix != NULL) {
86 			xmlFree(localname);
87 			xmlFree(prefix);
88 	        php_dom_throw_error(NAMESPACE_ERR, true);
89 	        RETURN_THROWS();
90 	    }
91 		nodep = xmlNewNode(NULL, BAD_CAST name);
92 	}
93 
94 	if (!nodep) {
95 		php_dom_throw_error(INVALID_STATE_ERR, true);
96 		RETURN_THROWS();
97 	}
98 
99 	if (value_len > 0) {
100 		xmlNodeSetContentLen(nodep, BAD_CAST value, value_len);
101 	}
102 
103 	intern = Z_DOMOBJ_P(ZEND_THIS);
104 	oldnode = dom_object_get_node(intern);
105 	if (oldnode != NULL) {
106 		php_libxml_node_decrement_resource((php_libxml_node_object *)intern);
107 	}
108 	php_libxml_increment_node_ptr((php_libxml_node_object *)intern, nodep, (void *)intern);
109 }
110 /* }}} end DOMElement::__construct */
111 
112 /* {{{ tagName	string
113 readonly=yes
114 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-104682815
115 Modern spec URL: https://dom.spec.whatwg.org/#dom-element-tagname
116 Since:
117 */
dom_element_tag_name_read(dom_object * obj,zval * retval)118 zend_result dom_element_tag_name_read(dom_object *obj, zval *retval)
119 {
120 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
121 
122 	bool uppercase = php_dom_follow_spec_intern(obj) && php_dom_ns_is_html_and_document_is_html(nodep);
123 
124 	zend_string *result = dom_node_get_node_name_attribute_or_element((const xmlNode *) nodep, uppercase);
125 	ZVAL_NEW_STR(retval, result);
126 
127 	return SUCCESS;
128 }
129 
130 /* }}} */
131 
dom_element_reflected_attribute_read(dom_object * obj,zval * retval,const char * name)132 static zend_result dom_element_reflected_attribute_read(dom_object *obj, zval *retval, const char *name)
133 {
134 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
135 
136 	xmlChar *content = xmlGetNoNsProp(nodep, (const xmlChar *) name);
137 	if (content == NULL) {
138 		ZVAL_EMPTY_STRING(retval);
139 		return SUCCESS;
140 	}
141 
142 	ZVAL_STRING(retval, (const char *) content);
143 	xmlFree(content);
144 
145 	return SUCCESS;
146 }
147 
dom_element_reflected_attribute_write(dom_object * obj,zval * newval,const char * name)148 static xmlAttrPtr dom_element_reflected_attribute_write(dom_object *obj, zval *newval, const char *name)
149 {
150 	xmlNode *nodep = dom_object_get_node(obj);
151 
152 	if (nodep == NULL) {
153 		php_dom_throw_error(INVALID_STATE_ERR, true);
154 		return NULL;
155 	}
156 
157 	/* Typed property, so it is a string already */
158 	ZEND_ASSERT(Z_TYPE_P(newval) == IS_STRING);
159 	return xmlSetNsProp(nodep, NULL, (const xmlChar *) name, (const xmlChar *) Z_STRVAL_P(newval));
160 }
161 
162 /* {{{ className	string
163 URL: https://dom.spec.whatwg.org/#dom-element-classname
164 Since:
165 */
dom_element_class_name_read(dom_object * obj,zval * retval)166 zend_result dom_element_class_name_read(dom_object *obj, zval *retval)
167 {
168 	return dom_element_reflected_attribute_read(obj, retval, "class");
169 }
170 
dom_element_class_name_write(dom_object * obj,zval * newval)171 zend_result dom_element_class_name_write(dom_object *obj, zval *newval)
172 {
173 	if (dom_element_reflected_attribute_write(obj, newval, "class")) {
174 		return SUCCESS;
175 	}
176 	return FAILURE;
177 }
178 /* }}} */
179 
180 /* {{{ classList	TokenList
181 URL: https://dom.spec.whatwg.org/#dom-element-classlist
182 */
dom_element_class_list_read(dom_object * obj,zval * retval)183 zend_result dom_element_class_list_read(dom_object *obj, zval *retval)
184 {
185 	const uint32_t PROP_INDEX = 0;
186 
187 #if ZEND_DEBUG
188 	zend_string *class_list_str = ZSTR_INIT_LITERAL("classList", false);
189 	const zend_property_info *prop_info = zend_get_property_info(dom_modern_element_class_entry, class_list_str, 0);
190 	zend_string_release_ex(class_list_str, false);
191 	ZEND_ASSERT(OBJ_PROP_TO_NUM(prop_info->offset) == PROP_INDEX);
192 #endif
193 
194 	zval *cached_token_list = OBJ_PROP_NUM(&obj->std, PROP_INDEX);
195 	if (Z_ISUNDEF_P(cached_token_list)) {
196 		object_init_ex(cached_token_list, dom_token_list_class_entry);
197 		dom_token_list_object *intern = php_dom_token_list_from_obj(Z_OBJ_P(cached_token_list));
198 		dom_token_list_ctor(intern, obj);
199 	}
200 
201 	ZVAL_OBJ_COPY(retval, Z_OBJ_P(cached_token_list));
202 
203 	return SUCCESS;
204 }
205 /* }}} */
206 
207 /* {{{ id	string
208 URL: https://dom.spec.whatwg.org/#dom-element-id
209 Since:
210 */
dom_element_id_read(dom_object * obj,zval * retval)211 zend_result dom_element_id_read(dom_object *obj, zval *retval)
212 {
213 	return dom_element_reflected_attribute_read(obj, retval, "id");
214 }
215 
216 static void php_set_attribute_id(xmlAttrPtr attrp, bool is_id, php_libxml_ref_obj *document);
217 
dom_element_id_write(dom_object * obj,zval * newval)218 zend_result dom_element_id_write(dom_object *obj, zval *newval)
219 {
220 	xmlAttrPtr attr = dom_element_reflected_attribute_write(obj, newval, "id");
221 	if (!attr) {
222 		return FAILURE;
223 	}
224 	php_set_attribute_id(attr, true, obj->document);
225 	return SUCCESS;
226 }
227 /* }}} */
228 
229 /* {{{ schemaTypeInfo	typeinfo
230 readonly=yes
231 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Element-schemaTypeInfo
232 Since: DOM Level 3
233 */
dom_element_schema_type_info_read(dom_object * obj,zval * retval)234 zend_result dom_element_schema_type_info_read(dom_object *obj, zval *retval)
235 {
236 	ZVAL_NULL(retval);
237 	return SUCCESS;
238 }
239 
240 /* }}} */
241 
242 /* Note: the object returned is not necessarily a node, but can be an attribute or a namespace declaration. */
dom_get_attribute_or_nsdecl(dom_object * intern,xmlNodePtr elem,const xmlChar * name,size_t name_len)243 static xmlNodePtr dom_get_attribute_or_nsdecl(dom_object *intern, xmlNodePtr elem, const xmlChar *name, size_t name_len) /* {{{ */
244 {
245 	if (!php_dom_follow_spec_intern(intern)) {
246 		int len;
247 		const xmlChar *nqname = xmlSplitQName3(name, &len);
248 
249 		if (nqname != NULL) {
250 			xmlNsPtr ns;
251 			if (strncmp((const char *) name, "xmlns:", len + 1) == 0) {
252 				ns = elem->nsDef;
253 				while (ns) {
254 					if (xmlStrEqual(ns->prefix, nqname)) {
255 						break;
256 					}
257 					ns = ns->next;
258 				}
259 				return (xmlNodePtr)ns;
260 			}
261 			xmlChar *prefix = xmlStrndup(name, len);
262 			ns = xmlSearchNs(elem->doc, elem, prefix);
263 			if (prefix != NULL) {
264 				xmlFree(prefix);
265 			}
266 			if (ns != NULL) {
267 				return (xmlNodePtr)xmlHasNsProp(elem, nqname, ns->href);
268 			}
269 		} else {
270 			if (xmlStrEqual(name, BAD_CAST "xmlns")) {
271 				xmlNsPtr nsPtr = elem->nsDef;
272 				while (nsPtr) {
273 					if (nsPtr->prefix == NULL) {
274 						return (xmlNodePtr)nsPtr;
275 					}
276 					nsPtr = nsPtr->next;
277 				}
278 				return NULL;
279 			}
280 		}
281 		return (xmlNodePtr) xmlHasNsProp(elem, name, NULL);
282 	} else {
283 		return (xmlNodePtr) php_dom_get_attribute_node(elem, name, name_len);
284 	}
285 }
286 /* }}} */
287 
288 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-666EE0F9
289 Modern spec URL: https://dom.spec.whatwg.org/#dom-element-getattribute
290 Since:
291 */
PHP_METHOD(DOMElement,getAttribute)292 PHP_METHOD(DOMElement, getAttribute)
293 {
294 	zval *id;
295 	xmlNode *nodep;
296 	char *name;
297 	xmlChar *value = NULL;
298 	dom_object *intern;
299 	xmlNodePtr attr;
300 	size_t name_len;
301 	bool should_free = false;
302 
303 	id = ZEND_THIS;
304 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) {
305 		RETURN_THROWS();
306 	}
307 
308 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
309 
310 	attr = dom_get_attribute_or_nsdecl(intern, nodep, BAD_CAST name, name_len);
311 	if (attr) {
312 		switch (attr->type) {
313 			case XML_ATTRIBUTE_NODE:
314 				value = xmlNodeListGetString(attr->doc, attr->children, 1);
315 				should_free = true;
316 				break;
317 			case XML_NAMESPACE_DECL:
318 				value = BAD_CAST ((xmlNsPtr)attr)->href;
319 				should_free = false;
320 				break;
321 			default:
322 				value = BAD_CAST ((xmlAttributePtr)attr)->defaultValue;
323 				should_free = false;
324 		}
325 	}
326 
327 	if (value == NULL) {
328 		if (php_dom_follow_spec_intern(intern)) {
329 			RETURN_NULL();
330 		}
331 		RETURN_EMPTY_STRING();
332 	} else {
333 		RETVAL_STRING((char *)value);
334 		if (should_free) {
335 			xmlFree(value);
336 		}
337 	}
338 }
339 /* }}} end dom_element_get_attribute */
340 
341 /* {{{ URL: https://dom.spec.whatwg.org/#dom-element-getattributenames
342 Since:
343 */
PHP_METHOD(DOMElement,getAttributeNames)344 PHP_METHOD(DOMElement, getAttributeNames)
345 {
346 	zval *id;
347 	xmlNode *nodep;
348 	dom_object *intern;
349 	zval tmp;
350 
351 	if (zend_parse_parameters_none() == FAILURE) {
352 		RETURN_THROWS();
353 	}
354 
355 	DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
356 
357 	array_init(return_value);
358 	HashTable *ht = Z_ARRVAL_P(return_value);
359 	zend_hash_real_init_packed(ht);
360 
361 	if (!php_dom_follow_spec_intern(intern)) {
362 		for (xmlNsPtr nsptr = nodep->nsDef; nsptr; nsptr = nsptr->next) {
363 			const char *prefix = (const char *) nsptr->prefix;
364 			ZVAL_NEW_STR(&tmp, dom_node_concatenated_name_helper(strlen(prefix), prefix, strlen("xmlns"), (const char *) "xmlns"));
365 			zend_hash_next_index_insert(ht, &tmp);
366 		}
367 	}
368 
369 	for (xmlAttrPtr attr = nodep->properties; attr; attr = attr->next) {
370 		ZVAL_NEW_STR(&tmp, dom_node_get_node_name_attribute_or_element((const xmlNode *) attr, false));
371 		zend_hash_next_index_insert(ht, &tmp);
372 	}
373 }
374 /* }}} end DOMElement::getAttributeNames() */
375 
dom_create_attribute(xmlNodePtr nodep,const char * name,const char * value)376 static xmlNodePtr dom_create_attribute(xmlNodePtr nodep, const char *name, const char* value)
377 {
378 	if (xmlStrEqual(BAD_CAST name, BAD_CAST "xmlns")) {
379 		return (xmlNodePtr) xmlNewNs(nodep, BAD_CAST value, NULL);
380 	} else {
381 		return (xmlNodePtr) xmlSetProp(nodep, BAD_CAST name, BAD_CAST value);
382 	}
383 }
384 
dom_check_register_attribute_id(xmlAttrPtr attr,php_libxml_ref_obj * document)385 static void dom_check_register_attribute_id(xmlAttrPtr attr, php_libxml_ref_obj *document)
386 {
387 	dom_mark_ids_modified(document);
388 
389 	if (attr->atype != XML_ATTRIBUTE_ID && attr->doc->type == XML_HTML_DOCUMENT_NODE && attr->ns == NULL && xmlStrEqual(attr->name, BAD_CAST "id")) {
390 		/* To respect XML's ID behaviour, we only do this registration for HTML documents. */
391 		attr->atype = XML_ATTRIBUTE_ID;
392 	}
393 }
394 
395 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-F68F082
396 Modern spec URL: https://dom.spec.whatwg.org/#dom-element-setattribute
397 Since:
398 */
PHP_METHOD(DOMElement,setAttribute)399 PHP_METHOD(DOMElement, setAttribute)
400 {
401 	zval *id;
402 	xmlNode *nodep;
403 	int name_valid;
404 	size_t name_len, value_len;
405 	dom_object *intern;
406 	char *name, *value;
407 
408 	id = ZEND_THIS;
409 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &name, &name_len, &value, &value_len) == FAILURE) {
410 		RETURN_THROWS();
411 	}
412 
413 	if (name_len == 0) {
414 		zend_argument_must_not_be_empty_error(1);
415 		RETURN_THROWS();
416 	}
417 
418 	name_valid = xmlValidateName(BAD_CAST name, 0);
419 	if (name_valid != 0) {
420 		php_dom_throw_error(INVALID_CHARACTER_ERR, true);
421 		RETURN_THROWS();
422 	}
423 
424 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
425 
426 	if (php_dom_follow_spec_intern(intern)) {
427 		xmlChar *name_processed = BAD_CAST name;
428 		if (php_dom_ns_is_html_and_document_is_html(nodep)) {
429 			char *lowercase_copy = zend_str_tolower_dup_ex(name, name_len);
430 			if (lowercase_copy != NULL) {
431 				name_processed = BAD_CAST lowercase_copy;
432 			}
433 		}
434 
435 		/* Can't use xmlSetNsProp unconditionally here because that doesn't take into account the qualified name matching... */
436 		xmlAttrPtr attr = php_dom_get_attribute_node(nodep, BAD_CAST name, name_len);
437 		if (attr != NULL) {
438 			dom_attr_value_will_change(intern, attr);
439 			dom_remove_all_children((xmlNodePtr) attr);
440 			xmlNodePtr node = xmlNewDocText(attr->doc, BAD_CAST value);
441 			xmlAddChild((xmlNodePtr) attr, node);
442 		} else {
443 			attr = xmlSetNsProp(nodep, NULL, name_processed, BAD_CAST value);
444 			if (EXPECTED(attr != NULL)) {
445 				dom_check_register_attribute_id(attr, intern->document);
446 			}
447 		}
448 
449 		if (name_processed != BAD_CAST name) {
450 			efree(name_processed);
451 		}
452 	} else {
453 		xmlNodePtr attr = dom_get_attribute_or_nsdecl(intern, nodep, BAD_CAST name, name_len);
454 		if (attr != NULL) {
455 			switch (attr->type) {
456 				case XML_ATTRIBUTE_NODE:
457 					dom_attr_value_will_change(intern, (xmlAttrPtr) attr);
458 					node_list_unlink(attr->children);
459 					break;
460 				case XML_NAMESPACE_DECL:
461 					RETURN_FALSE;
462 				EMPTY_SWITCH_DEFAULT_CASE();
463 			}
464 		}
465 
466 		attr = dom_create_attribute(nodep, name, value);
467 		if (!attr) {
468 			zend_argument_value_error(1, "must be a valid XML attribute");
469 			RETURN_THROWS();
470 		}
471 		if (attr->type == XML_NAMESPACE_DECL) {
472 			RETURN_TRUE;
473 		}
474 
475 		DOM_RET_OBJ(attr, intern);
476 	}
477 }
478 /* }}} end dom_element_set_attribute */
479 
480 typedef struct dom_deep_ns_redef_item {
481 	xmlNodePtr current_node;
482 	xmlNsPtr defined_ns;
483 } dom_deep_ns_redef_item;
484 
485 /* Reconciliation for a *single* namespace, but reconciles *closest* to the subtree needing it. */
dom_deep_ns_redef(xmlNodePtr node,xmlNsPtr ns_to_redefine)486 static void dom_deep_ns_redef(xmlNodePtr node, xmlNsPtr ns_to_redefine)
487 {
488 	size_t worklist_capacity = 128;
489 	dom_deep_ns_redef_item *worklist = emalloc(sizeof(dom_deep_ns_redef_item) * worklist_capacity);
490 	worklist[0].current_node = node;
491 	worklist[0].defined_ns = NULL;
492 	size_t worklist_size = 1;
493 
494 	while (worklist_size > 0) {
495 		worklist_size--;
496 		dom_deep_ns_redef_item *current_worklist_item = &worklist[worklist_size];
497 		ZEND_ASSERT(current_worklist_item->current_node->type == XML_ELEMENT_NODE);
498 		xmlNsPtr defined_ns = current_worklist_item->defined_ns;
499 
500 		if (current_worklist_item->current_node->ns == ns_to_redefine) {
501 			if (defined_ns == NULL) {
502 				defined_ns = xmlNewNs(current_worklist_item->current_node, ns_to_redefine->href, ns_to_redefine->prefix);
503 			}
504 			current_worklist_item->current_node->ns = defined_ns;
505 		}
506 
507 		for (xmlAttrPtr attr = current_worklist_item->current_node->properties; attr; attr = attr->next) {
508 			if (attr->ns == ns_to_redefine) {
509 				if (defined_ns == NULL) {
510 					defined_ns = xmlNewNs(current_worklist_item->current_node, ns_to_redefine->href, ns_to_redefine->prefix);
511 				}
512 				attr->ns = defined_ns;
513 			}
514 		}
515 
516 		for (xmlNodePtr child = current_worklist_item->current_node->children; child; child = child->next) {
517 			if (child->type != XML_ELEMENT_NODE) {
518 				continue;
519 			}
520 			if (worklist_size == worklist_capacity) {
521 				if (UNEXPECTED(worklist_capacity >= SIZE_MAX / 3 * 2 / sizeof(dom_deep_ns_redef_item))) {
522 					/* Shouldn't be possible to hit, but checked for safety anyway */
523 					goto out;
524 				}
525 				worklist_capacity = worklist_capacity * 3 / 2;
526 				worklist = erealloc(worklist, sizeof(dom_deep_ns_redef_item) * worklist_capacity);
527 			}
528 			worklist[worklist_size].current_node = child;
529 			worklist[worklist_size].defined_ns = defined_ns;
530 			worklist_size++;
531 		}
532 	}
533 
534 out:
535 	efree(worklist);
536 }
537 
dom_remove_attribute(xmlNodePtr thisp,xmlNodePtr attrp)538 static bool dom_remove_attribute(xmlNodePtr thisp, xmlNodePtr attrp)
539 {
540 	ZEND_ASSERT(thisp != NULL);
541 	ZEND_ASSERT(attrp != NULL);
542 
543 	switch (attrp->type) {
544 		case XML_ATTRIBUTE_NODE:
545 			if (php_dom_object_get_data(attrp) == NULL) {
546 				node_list_unlink(attrp->children);
547 				xmlUnlinkNode(attrp);
548 				xmlFreeProp((xmlAttrPtr)attrp);
549 			} else {
550 				xmlUnlinkNode(attrp);
551 			}
552 			break;
553 		case XML_NAMESPACE_DECL: {
554 			/* They will always be removed, but can be re-added.
555 			 *
556 			 * If any reference was left to the namespace, the only effect is that
557 			 * the definition is potentially moved closer to the element using it.
558 			 * If no reference was left, it is actually removed. */
559 			xmlNsPtr ns = (xmlNsPtr) attrp;
560 			if (thisp->nsDef == ns) {
561 				thisp->nsDef = ns->next;
562 			} else if (thisp->nsDef != NULL) {
563 				xmlNsPtr prev = thisp->nsDef;
564 				xmlNsPtr cur = prev->next;
565 				while (cur) {
566 					if (cur == ns) {
567 						prev->next = cur->next;
568 						break;
569 					}
570 					prev = cur;
571 					cur = cur->next;
572 				}
573 			} else {
574 				/* defensive: attrp not defined in thisp ??? */
575 #if ZEND_DEBUG
576 				ZEND_UNREACHABLE();
577 #endif
578 				break; /* defensive */
579 			}
580 
581 			ns->next = NULL;
582 			php_libxml_set_old_ns(thisp->doc, ns); /* note: can't deallocate as it might be referenced by a "fake namespace node" */
583 			/* xmlReconciliateNs() redefines at the top of the tree instead of closest to the child, own reconciliation here.
584 			 * Similarly, the DOM version has other issues too (see dom_libxml_reconcile_ensure_namespaces_are_declared). */
585 			dom_deep_ns_redef(thisp, ns);
586 
587 			break;
588 		}
589 		EMPTY_SWITCH_DEFAULT_CASE();
590 	}
591 	return true;
592 }
593 
594 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-6D6AC0F9
595 Modern spec URL: https://dom.spec.whatwg.org/#dom-element-removeattribute
596 Since:
597 */
PHP_METHOD(DOMElement,removeAttribute)598 PHP_METHOD(DOMElement, removeAttribute)
599 {
600 	xmlNodePtr nodep, attrp;
601 	dom_object *intern;
602 	size_t name_len;
603 	char *name;
604 
605 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) {
606 		RETURN_THROWS();
607 	}
608 
609 	DOM_GET_OBJ(nodep, ZEND_THIS, xmlNodePtr, intern);
610 
611 	attrp = dom_get_attribute_or_nsdecl(intern, nodep, BAD_CAST name, name_len);
612 	if (attrp == NULL) {
613 		RETURN_FALSE;
614 	}
615 
616 	RETURN_BOOL(dom_remove_attribute(nodep, attrp));
617 }
618 
PHP_METHOD(Dom_Element,removeAttribute)619 PHP_METHOD(Dom_Element, removeAttribute)
620 {
621 	xmlNodePtr nodep, attrp;
622 	dom_object *intern;
623 	size_t name_len;
624 	char *name;
625 
626 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) {
627 		RETURN_THROWS();
628 	}
629 
630 	DOM_GET_OBJ(nodep, ZEND_THIS, xmlNodePtr, intern);
631 
632 	attrp = dom_get_attribute_or_nsdecl(intern, nodep, BAD_CAST name, name_len);
633 	if (attrp != NULL) {
634 		dom_remove_attribute(nodep, attrp);
635 	}
636 }
637 /* }}} end dom_element_remove_attribute */
638 
639 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-217A91B8
640 Modern spec URL: https://dom.spec.whatwg.org/#dom-element-getattributenode
641 Since:
642 */
PHP_METHOD(DOMElement,getAttributeNode)643 PHP_METHOD(DOMElement, getAttributeNode)
644 {
645 	zval *id;
646 	xmlNodePtr nodep, attrp;
647 	size_t name_len;
648 	dom_object *intern;
649 	char *name;
650 
651 	id = ZEND_THIS;
652 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) {
653 		RETURN_THROWS();
654 	}
655 
656 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
657 
658 	attrp = dom_get_attribute_or_nsdecl(intern, nodep, BAD_CAST name, name_len);
659 	if (attrp == NULL) {
660 		if (php_dom_follow_spec_intern(intern)) {
661 			RETURN_NULL();
662 		}
663 		RETURN_FALSE;
664 	}
665 
666 	if (attrp->type == XML_NAMESPACE_DECL) {
667 		xmlNsPtr original = (xmlNsPtr) attrp;
668 		/* Keep parent alive, because we're a fake child. */
669 		GC_ADDREF(&intern->std);
670 		(void) php_dom_create_fake_namespace_decl(nodep, original, return_value, intern);
671 	} else {
672 		DOM_RET_OBJ((xmlNodePtr) attrp, intern);
673 	}
674 }
675 /* }}} end dom_element_get_attribute_node */
676 
dom_element_set_attribute_node_common(INTERNAL_FUNCTION_PARAMETERS,bool use_ns,bool modern)677 static void dom_element_set_attribute_node_common(INTERNAL_FUNCTION_PARAMETERS, bool use_ns, bool modern)
678 {
679 	zval *id, *node;
680 	xmlNode *nodep;
681 	xmlNs *nsp;
682 	xmlAttr *attrp, *existattrp = NULL;
683 	dom_object *intern, *attrobj, *oldobj;
684 
685 	id = ZEND_THIS;
686 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node, dom_get_attr_ce(modern)) == FAILURE) {
687 		RETURN_THROWS();
688 	}
689 
690 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
691 	DOM_GET_OBJ(attrp, node, xmlAttrPtr, attrobj);
692 
693 	/* ZPP Guarantees that a DOMAttr class is given, as it is converted to a xmlAttr
694 	 * to pass to libxml (see http://www.xmlsoft.org/html/libxml-tree.html#xmlAttr)
695 	 * if it is not of type XML_ATTRIBUTE_NODE it indicates a bug somewhere */
696 	ZEND_ASSERT(attrp->type == XML_ATTRIBUTE_NODE);
697 
698 	if (modern) {
699 		if (attrp->parent != NULL && attrp->parent != nodep) {
700 			php_dom_throw_error(INUSE_ATTRIBUTE_ERR, /* strict */ true);
701 			RETURN_THROWS();
702 		}
703 		if (attrp->doc != NULL && attrp->doc != nodep->doc) {
704 			php_dom_adopt_node((xmlNodePtr) attrp, intern, nodep->doc);
705 		}
706 	} else {
707 		if (!(attrp->doc == NULL || attrp->doc == nodep->doc)) {
708 			php_dom_throw_error(WRONG_DOCUMENT_ERR, dom_get_strict_error(intern->document));
709 			RETURN_FALSE;
710 		}
711 	}
712 
713 	nsp = attrp->ns;
714 	if (use_ns && nsp != NULL) {
715 		existattrp = xmlHasNsProp(nodep, attrp->name, nsp->href);
716 	} else {
717 		existattrp = xmlHasProp(nodep, attrp->name);
718 	}
719 
720 	if (existattrp != NULL && existattrp->type != XML_ATTRIBUTE_DECL) {
721 		if ((oldobj = php_dom_object_get_data((xmlNodePtr) existattrp)) != NULL &&
722 			((php_libxml_node_ptr *)oldobj->ptr)->node == (xmlNodePtr) attrp)
723 		{
724 			RETURN_NULL();
725 		}
726 		xmlUnlinkNode((xmlNodePtr) existattrp);
727 	}
728 
729 	if (attrp->parent != NULL) {
730 		xmlUnlinkNode((xmlNodePtr) attrp);
731 	}
732 
733 	if (attrp->doc == NULL && nodep->doc != NULL) {
734 		attrobj->document = intern->document;
735 		php_libxml_increment_doc_ref((php_libxml_node_object *)attrobj, NULL);
736 	}
737 
738 	xmlAddChild(nodep, (xmlNodePtr) attrp);
739 	if (!modern) {
740 		dom_mark_ids_modified(intern->document);
741 		php_dom_reconcile_attribute_namespace_after_insertion(attrp);
742 	} else {
743 		dom_check_register_attribute_id(attrp, intern->document);
744 	}
745 
746 	/* Returns old property if removed otherwise NULL */
747 	if (existattrp != NULL) {
748 		DOM_RET_OBJ((xmlNodePtr) existattrp, intern);
749 	} else {
750 		RETURN_NULL();
751 	}
752 }
753 
754 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-887236154
755 Modern spec URL: https://dom.spec.whatwg.org/#dom-element-setattributenode
756 Since:
757 */
PHP_METHOD(DOMElement,setAttributeNode)758 PHP_METHOD(DOMElement, setAttributeNode)
759 {
760 	dom_element_set_attribute_node_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, /* use_ns */ false, /* modern */ false);
761 }
762 /* }}} end dom_element_set_attribute_node */
763 
764 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-D589198
765 Since:
766 */
dom_element_remove_attribute_node(INTERNAL_FUNCTION_PARAMETERS,zend_class_entry * node_ce)767 static void dom_element_remove_attribute_node(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *node_ce)
768 {
769 	zval *node;
770 	xmlNode *nodep;
771 	xmlAttr *attrp;
772 	dom_object *intern, *attrobj;
773 
774 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node, node_ce) == FAILURE) {
775 		RETURN_THROWS();
776 	}
777 
778 	DOM_GET_OBJ(nodep, ZEND_THIS, xmlNodePtr, intern);
779 
780 	DOM_GET_OBJ(attrp, node, xmlAttrPtr, attrobj);
781 
782 	ZEND_ASSERT(attrp->type == XML_ATTRIBUTE_NODE);
783 
784 	if (attrp->parent != nodep) {
785 		php_dom_throw_error(NOT_FOUND_ERR, dom_get_strict_error(intern->document));
786 		RETURN_FALSE;
787 	}
788 
789 	xmlUnlinkNode((xmlNodePtr) attrp);
790 
791 	DOM_RET_OBJ((xmlNodePtr) attrp, intern);
792 }
793 
PHP_METHOD(DOMElement,removeAttributeNode)794 PHP_METHOD(DOMElement, removeAttributeNode)
795 {
796 	dom_element_remove_attribute_node(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_attr_class_entry);
797 }
798 
PHP_METHOD(Dom_Element,removeAttributeNode)799 PHP_METHOD(Dom_Element, removeAttributeNode)
800 {
801 	dom_element_remove_attribute_node(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_modern_attr_class_entry);
802 }
803 /* }}} end dom_element_remove_attribute_node */
804 
805 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1938918D
806 Modern spec URL: https://dom.spec.whatwg.org/#concept-getelementsbytagname
807 Since:
808 */
dom_element_get_elements_by_tag_name(INTERNAL_FUNCTION_PARAMETERS,bool modern)809 static void dom_element_get_elements_by_tag_name(INTERNAL_FUNCTION_PARAMETERS, bool modern)
810 {
811 	size_t name_len;
812 	dom_object *intern, *namednode;
813 	char *name;
814 
815 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) {
816 		RETURN_THROWS();
817 	}
818 
819 	DOM_GET_THIS_INTERN(intern);
820 
821 	if (modern) {
822 		php_dom_create_iterator(return_value, DOM_HTMLCOLLECTION, true);
823 	} else {
824 		php_dom_create_iterator(return_value, DOM_NODELIST, false);
825 	}
826 	namednode = Z_DOMOBJ_P(return_value);
827 	dom_namednode_iter(intern, 0, namednode, NULL, name, name_len, NULL, 0);
828 }
829 
PHP_METHOD(DOMElement,getElementsByTagName)830 PHP_METHOD(DOMElement, getElementsByTagName)
831 {
832 	dom_element_get_elements_by_tag_name(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
833 }
834 
PHP_METHOD(Dom_Element,getElementsByTagName)835 PHP_METHOD(Dom_Element, getElementsByTagName)
836 {
837 	dom_element_get_elements_by_tag_name(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
838 }
839 /* }}} end dom_element_get_elements_by_tag_name */
840 
841 /* should_free_result must be initialized to false */
dom_get_attribute_ns(dom_object * intern,xmlNodePtr elemp,const char * uri,size_t uri_len,const char * name,bool * should_free_result)842 static const xmlChar *dom_get_attribute_ns(dom_object *intern, xmlNodePtr elemp, const char *uri, size_t uri_len, const char *name, bool *should_free_result)
843 {
844 	bool follow_spec = php_dom_follow_spec_intern(intern);
845 	if (follow_spec && uri_len == 0) {
846 		uri = NULL;
847 	}
848 
849 	xmlChar *strattr = xmlGetNsProp(elemp, BAD_CAST name, BAD_CAST uri);
850 
851 	if (strattr != NULL) {
852 		*should_free_result = true;
853 		return strattr;
854 	} else {
855 		if (!follow_spec && xmlStrEqual(BAD_CAST uri, BAD_CAST DOM_XMLNS_NS_URI)) {
856 			xmlNsPtr nsptr = dom_get_nsdecl(elemp, BAD_CAST name);
857 			if (nsptr != NULL) {
858 				return nsptr->href;
859 			} else {
860 				return NULL;
861 			}
862 		} else {
863 			return NULL;
864 		}
865 	}
866 }
867 
868 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-ElGetAttrNS
869 Modern spec URL: https://dom.spec.whatwg.org/#dom-element-getattributens
870 Since: DOM Level 2
871 */
PHP_METHOD(DOMElement,getAttributeNS)872 PHP_METHOD(DOMElement, getAttributeNS)
873 {
874 	zval *id;
875 	xmlNodePtr elemp;
876 	dom_object *intern;
877 	size_t uri_len = 0, name_len = 0;
878 	char *uri, *name;
879 
880 	id = ZEND_THIS;
881 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!s", &uri, &uri_len, &name, &name_len) == FAILURE) {
882 		RETURN_THROWS();
883 	}
884 
885 	DOM_GET_OBJ(elemp, id, xmlNodePtr, intern);
886 
887 	bool should_free_result = false;
888 	const xmlChar *result = dom_get_attribute_ns(intern, elemp, uri, uri_len, name, &should_free_result);
889 	if (result == NULL) {
890 		if (php_dom_follow_spec_intern(intern)) {
891 			RETURN_NULL();
892 		}
893 		RETURN_EMPTY_STRING();
894 	} else {
895 		RETVAL_STRING((const char *) result);
896 		if (should_free_result) {
897 			xmlFree(BAD_CAST result);
898 		}
899 	}
900 }
901 /* }}} end dom_element_get_attribute_ns */
902 
dom_set_attribute_ns_legacy(dom_object * intern,xmlNodePtr elemp,char * uri,size_t uri_len,char * name,size_t name_len,const char * value)903 static void dom_set_attribute_ns_legacy(dom_object *intern, xmlNodePtr elemp, char *uri, size_t uri_len, char *name, size_t name_len, const char *value)
904 {
905 	if (name_len == 0) {
906 		zend_argument_must_not_be_empty_error(2);
907 		return;
908 	}
909 
910 	xmlNodePtr nodep = NULL;
911 	xmlNsPtr nsptr;
912 	xmlAttr *attr;
913 	char *localname = NULL, *prefix = NULL;
914 	int is_xmlns = 0, name_valid;
915 	bool stricterror = dom_get_strict_error(intern->document);
916 
917 	int errorcode = dom_check_qname(name, &localname, &prefix, uri_len, name_len);
918 
919 	if (errorcode == 0) {
920 		dom_mark_ids_modified(intern->document);
921 
922 		if (uri_len > 0) {
923 			nodep = (xmlNodePtr) xmlHasNsProp(elemp, BAD_CAST localname, BAD_CAST uri);
924 			if (nodep != NULL && nodep->type != XML_ATTRIBUTE_DECL) {
925 				node_list_unlink(nodep->children);
926 			}
927 
928 			if ((xmlStrEqual(BAD_CAST prefix, BAD_CAST "xmlns") ||
929 				(prefix == NULL && xmlStrEqual(BAD_CAST localname, BAD_CAST "xmlns"))) &&
930 				xmlStrEqual(BAD_CAST uri, BAD_CAST DOM_XMLNS_NS_URI)) {
931 				is_xmlns = 1;
932 				if (prefix == NULL) {
933 					nsptr = dom_get_nsdecl(elemp, NULL);
934 				} else {
935 					nsptr = dom_get_nsdecl(elemp, BAD_CAST localname);
936 				}
937 			} else {
938 				nsptr = xmlSearchNsByHref(elemp->doc, elemp, BAD_CAST uri);
939 				if (nsptr && nsptr->prefix == NULL) {
940 					xmlNsPtr tmpnsptr;
941 
942 					tmpnsptr = nsptr->next;
943 					while (tmpnsptr) {
944 						if ((tmpnsptr->prefix != NULL) && (tmpnsptr->href != NULL) &&
945 							(xmlStrEqual(tmpnsptr->href, BAD_CAST uri))) {
946 							nsptr = tmpnsptr;
947 							break;
948 						}
949 						tmpnsptr = tmpnsptr->next;
950 					}
951 					if (tmpnsptr == NULL) {
952 						nsptr = dom_get_ns_resolve_prefix_conflict(elemp, (const char *) nsptr->href);
953 					}
954 				}
955 			}
956 
957 			if (nsptr == NULL) {
958 				if (is_xmlns == 1) {
959 					xmlNewNs(elemp, BAD_CAST value, prefix == NULL ? NULL : BAD_CAST localname);
960 				} else {
961 					nsptr = dom_get_ns(elemp, uri, &errorcode, prefix);
962 				}
963 				xmlReconciliateNs(elemp->doc, elemp);
964 			} else {
965 				if (is_xmlns == 1) {
966 					if (nsptr->href) {
967 						xmlFree(BAD_CAST nsptr->href);
968 					}
969 					nsptr->href = xmlStrdup(BAD_CAST value);
970 				}
971 			}
972 
973 			if (errorcode == 0 && is_xmlns == 0) {
974 				xmlSetNsProp(elemp, nsptr, BAD_CAST localname, BAD_CAST value);
975 			}
976 		} else {
977 			name_valid = xmlValidateName(BAD_CAST localname, 0);
978 			if (name_valid != 0) {
979 				errorcode = INVALID_CHARACTER_ERR;
980 				stricterror = 1;
981 			} else {
982 				attr = xmlHasProp(elemp, BAD_CAST localname);
983 				if (attr != NULL && attr->type != XML_ATTRIBUTE_DECL) {
984 					node_list_unlink(attr->children);
985 				}
986 				xmlSetProp(elemp, BAD_CAST localname, BAD_CAST value);
987 			}
988 		}
989 	}
990 
991 	xmlFree(localname);
992 	if (prefix != NULL) {
993 		xmlFree(prefix);
994 	}
995 
996 	if (errorcode != 0) {
997 		php_dom_throw_error(errorcode, stricterror);
998 	}
999 }
1000 
1001 /* https://dom.spec.whatwg.org/#dom-element-setattributens */
dom_set_attribute_ns_modern(dom_object * intern,xmlNodePtr elemp,zend_string * uri,const zend_string * name,const char * value)1002 static void dom_set_attribute_ns_modern(dom_object *intern, xmlNodePtr elemp, zend_string *uri, const zend_string *name, const char *value)
1003 {
1004 	xmlChar *localname = NULL, *prefix = NULL;
1005 	int errorcode = dom_validate_and_extract(uri, name, &localname, &prefix);
1006 
1007 	if (errorcode == 0) {
1008 		php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
1009 		xmlNsPtr ns = php_dom_libxml_ns_mapper_get_ns_raw_prefix_string(ns_mapper, prefix, xmlStrlen(prefix), uri);
1010 		xmlAttrPtr attr = xmlSetNsProp(elemp, ns, localname, BAD_CAST value);
1011 		if (UNEXPECTED(attr == NULL)) {
1012 			php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
1013 		} else {
1014 			dom_check_register_attribute_id(attr, intern->document);
1015 		}
1016 	} else {
1017 		php_dom_throw_error(errorcode, /* strict */ true);
1018 	}
1019 
1020 	xmlFree(localname);
1021 	xmlFree(prefix);
1022 }
1023 
1024 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-ElSetAttrNS
1025 Modern spec URL: https://dom.spec.whatwg.org/#dom-element-setattributens
1026 Since: DOM Level 2
1027 */
PHP_METHOD(DOMElement,setAttributeNS)1028 PHP_METHOD(DOMElement, setAttributeNS)
1029 {
1030 	zval *id;
1031 	xmlNodePtr elemp;
1032 	size_t value_len = 0;
1033 	char *value;
1034 	zend_string *uri;
1035 	zend_string *name = NULL;
1036 	dom_object *intern;
1037 
1038 	id = ZEND_THIS;
1039 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S!Ss", &uri, &name, &value, &value_len) == FAILURE) {
1040 		RETURN_THROWS();
1041 	}
1042 
1043 	DOM_GET_OBJ(elemp, id, xmlNodePtr, intern);
1044 
1045 	if (php_dom_follow_spec_intern(intern)) {
1046 		dom_set_attribute_ns_modern(intern, elemp, uri, name, value);
1047 	} else {
1048 		dom_set_attribute_ns_legacy(intern, elemp, uri ? ZSTR_VAL(uri) : NULL, uri ? ZSTR_LEN(uri) : 0, ZSTR_VAL(name), ZSTR_LEN(name), value);
1049 	}
1050 }
1051 /* }}} end dom_element_set_attribute_ns */
1052 
dom_remove_eliminated_ns_single_element(xmlNodePtr node,xmlNsPtr eliminatedNs)1053 static void dom_remove_eliminated_ns_single_element(xmlNodePtr node, xmlNsPtr eliminatedNs)
1054 {
1055 	ZEND_ASSERT(node->type == XML_ELEMENT_NODE);
1056 	if (node->ns == eliminatedNs) {
1057 		node->ns = NULL;
1058 	}
1059 
1060 	for (xmlAttrPtr attr = node->properties; attr != NULL; attr = attr->next) {
1061 		if (attr->ns == eliminatedNs) {
1062 			attr->ns = NULL;
1063 		}
1064 	}
1065 }
1066 
dom_remove_eliminated_ns(xmlNodePtr node,xmlNsPtr eliminatedNs)1067 static void dom_remove_eliminated_ns(xmlNodePtr node, xmlNsPtr eliminatedNs)
1068 {
1069 	dom_remove_eliminated_ns_single_element(node, eliminatedNs);
1070 
1071 	xmlNodePtr base = node;
1072 	node = node->children;
1073 	while (node != NULL) {
1074 		ZEND_ASSERT(node != base);
1075 
1076 		if (node->type == XML_ELEMENT_NODE) {
1077 			dom_remove_eliminated_ns_single_element(node, eliminatedNs);
1078 		}
1079 
1080 		node = php_dom_next_in_tree_order(node, base);
1081 	}
1082 }
1083 
dom_eliminate_ns(xmlNodePtr nodep,xmlNsPtr nsptr)1084 static void dom_eliminate_ns(xmlNodePtr nodep, xmlNsPtr nsptr)
1085 {
1086 	if (nsptr->href != NULL) {
1087 		xmlFree((char *) nsptr->href);
1088 		nsptr->href = NULL;
1089 	}
1090 	if (nsptr->prefix != NULL) {
1091 		xmlFree((char *) nsptr->prefix);
1092 		nsptr->prefix = NULL;
1093 	}
1094 
1095 	/* Remove it from the list and move it to the old ns list */
1096 	xmlNsPtr current_ns = nodep->nsDef;
1097 	if (current_ns == nsptr) {
1098 		nodep->nsDef = nsptr->next;
1099 	} else {
1100 		do {
1101 			if (current_ns->next == nsptr) {
1102 				current_ns->next = nsptr->next;
1103 				break;
1104 			}
1105 			current_ns = current_ns->next;
1106 		} while (current_ns != NULL);
1107 	}
1108 	nsptr->next = NULL;
1109 	php_libxml_set_old_ns(nodep->doc, nsptr);
1110 
1111 	dom_remove_eliminated_ns(nodep, nsptr);
1112 }
1113 
1114 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-ElRemAtNS
1115 Modern spec URL: https://dom.spec.whatwg.org/#dom-element-removeattributens
1116 Since: DOM Level 2
1117 */
PHP_METHOD(DOMElement,removeAttributeNS)1118 PHP_METHOD(DOMElement, removeAttributeNS)
1119 {
1120 	zval *id;
1121 	xmlNode *nodep;
1122 	xmlAttr *attrp;
1123 	xmlNsPtr nsptr;
1124 	dom_object *intern;
1125 	size_t name_len, uri_len;
1126 	char *name, *uri;
1127 
1128 	id = ZEND_THIS;
1129 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!s", &uri, &uri_len, &name, &name_len) == FAILURE) {
1130 		RETURN_THROWS();
1131 	}
1132 
1133 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1134 
1135 	bool follow_spec = php_dom_follow_spec_intern(intern);
1136 	if (follow_spec && uri_len == 0) {
1137 		uri = NULL;
1138 	}
1139 
1140 	attrp = xmlHasNsProp(nodep, BAD_CAST name, BAD_CAST uri);
1141 
1142 	if (!follow_spec) {
1143 		nsptr = dom_get_nsdecl(nodep, BAD_CAST name);
1144 		if (nsptr != NULL) {
1145 			if (xmlStrEqual(BAD_CAST uri, nsptr->href)) {
1146 				dom_eliminate_ns(nodep, nsptr);
1147 			} else {
1148 				return;
1149 			}
1150 		}
1151 	}
1152 
1153 	if (attrp && attrp->type != XML_ATTRIBUTE_DECL) {
1154 		if (php_dom_object_get_data((xmlNodePtr) attrp) == NULL) {
1155 			node_list_unlink(attrp->children);
1156 			xmlUnlinkNode((xmlNodePtr) attrp);
1157 			xmlFreeProp(attrp);
1158 		} else {
1159 			xmlUnlinkNode((xmlNodePtr) attrp);
1160 		}
1161 	}
1162 }
1163 /* }}} end dom_element_remove_attribute_ns */
1164 
1165 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-ElGetAtNodeNS
1166 Modern spec URL: https://dom.spec.whatwg.org/#dom-element-getattributenodens
1167 Since: DOM Level 2
1168 */
PHP_METHOD(DOMElement,getAttributeNodeNS)1169 PHP_METHOD(DOMElement, getAttributeNodeNS)
1170 {
1171 	zval *id;
1172 	xmlNodePtr elemp;
1173 	xmlAttrPtr attrp;
1174 	dom_object *intern;
1175 	size_t uri_len, name_len;
1176 	char *uri, *name;
1177 
1178 	id = ZEND_THIS;
1179 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!s", &uri, &uri_len, &name, &name_len) == FAILURE) {
1180 		RETURN_THROWS();
1181 	}
1182 
1183 	DOM_GET_OBJ(elemp, id, xmlNodePtr, intern);
1184 
1185 	bool follow_spec = php_dom_follow_spec_intern(intern);
1186 	if (follow_spec && uri_len == 0) {
1187 		uri = NULL;
1188 	}
1189 
1190 	attrp = xmlHasNsProp(elemp, BAD_CAST name, BAD_CAST uri);
1191 
1192 	if (attrp == NULL) {
1193 		if (!follow_spec && xmlStrEqual(BAD_CAST uri, BAD_CAST DOM_XMLNS_NS_URI)) {
1194 			xmlNsPtr nsptr;
1195 			nsptr = dom_get_nsdecl(elemp, BAD_CAST name);
1196 			if (nsptr != NULL) {
1197 				/* Keep parent alive, because we're a fake child. */
1198 				GC_ADDREF(&intern->std);
1199 				(void) php_dom_create_fake_namespace_decl(elemp, nsptr, return_value, intern);
1200 			} else {
1201 				RETURN_NULL();
1202 			}
1203 		} else {
1204 			RETURN_NULL();
1205 		}
1206 	} else {
1207 		DOM_RET_OBJ((xmlNodePtr) attrp, intern);
1208 	}
1209 
1210 }
1211 /* }}} end dom_element_get_attribute_node_ns */
1212 
1213 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-ElSetAtNodeNS
1214 Modern spec URL: https://dom.spec.whatwg.org/#dom-element-setattributenodens
1215 Since: DOM Level 2
1216 */
PHP_METHOD(DOMElement,setAttributeNodeNS)1217 PHP_METHOD(DOMElement, setAttributeNodeNS)
1218 {
1219 	dom_element_set_attribute_node_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, /* use_ns */ true, /* modern */ false);
1220 }
1221 
PHP_METHOD(Dom_Element,setAttributeNodeNS)1222 PHP_METHOD(Dom_Element, setAttributeNodeNS)
1223 {
1224 	dom_element_set_attribute_node_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, /* use_ns */ true, /* modern */ true);
1225 }
1226 /* }}} end dom_element_set_attribute_node_ns */
1227 
1228 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-A6C90942
1229 Modern spec URL: https://dom.spec.whatwg.org/#concept-getelementsbytagnamens
1230 Since: DOM Level 2
1231 */
dom_element_get_elements_by_tag_name_ns(INTERNAL_FUNCTION_PARAMETERS,bool modern)1232 static void dom_element_get_elements_by_tag_name_ns(INTERNAL_FUNCTION_PARAMETERS, bool modern)
1233 {
1234 	size_t uri_len, name_len;
1235 	dom_object *intern, *namednode;
1236 	char *uri, *name;
1237 
1238 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!s", &uri, &uri_len, &name, &name_len) == FAILURE) {
1239 		RETURN_THROWS();
1240 	}
1241 
1242 	DOM_GET_THIS_INTERN(intern);
1243 
1244 	if (modern) {
1245 		php_dom_create_iterator(return_value, DOM_HTMLCOLLECTION, true);
1246 	} else {
1247 		php_dom_create_iterator(return_value, DOM_NODELIST, false);
1248 	}
1249 	namednode = Z_DOMOBJ_P(return_value);
1250 	dom_namednode_iter(intern, 0, namednode, NULL, name, name_len, uri ? uri : "", uri_len);
1251 }
1252 
PHP_METHOD(DOMElement,getElementsByTagNameNS)1253 PHP_METHOD(DOMElement, getElementsByTagNameNS)
1254 {
1255 	dom_element_get_elements_by_tag_name_ns(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1256 }
1257 
PHP_METHOD(Dom_Element,getElementsByTagNameNS)1258 PHP_METHOD(Dom_Element, getElementsByTagNameNS)
1259 {
1260 	dom_element_get_elements_by_tag_name_ns(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1261 }
1262 /* }}} end dom_element_get_elements_by_tag_name_ns */
1263 
1264 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-ElHasAttr
1265 Modern spec URL: https://dom.spec.whatwg.org/#dom-element-hasattribute
1266 Since: DOM Level 2
1267 */
PHP_METHOD(DOMElement,hasAttribute)1268 PHP_METHOD(DOMElement, hasAttribute)
1269 {
1270 	zval *id;
1271 	xmlNode *nodep;
1272 	dom_object *intern;
1273 	char *name;
1274 	size_t name_len;
1275 	xmlNodePtr attr;
1276 
1277 	id = ZEND_THIS;
1278 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) {
1279 		RETURN_THROWS();
1280 	}
1281 
1282 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1283 
1284 	attr = dom_get_attribute_or_nsdecl(intern, nodep, BAD_CAST name, name_len);
1285 	if (attr == NULL) {
1286 		RETURN_FALSE;
1287 	} else {
1288 		RETURN_TRUE;
1289 	}
1290 }
1291 /* }}} end dom_element_has_attribute */
1292 
1293 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-ElHasAttrNS
1294 Modern spec URL: https://dom.spec.whatwg.org/#dom-element-hasattributens
1295 Since: DOM Level 2
1296 */
PHP_METHOD(DOMElement,hasAttributeNS)1297 PHP_METHOD(DOMElement, hasAttributeNS)
1298 {
1299 	zval *id;
1300 	xmlNodePtr elemp;
1301 	dom_object *intern;
1302 	size_t uri_len, name_len;
1303 	char *uri, *name;
1304 
1305 	id = ZEND_THIS;
1306 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!s", &uri, &uri_len, &name, &name_len) == FAILURE) {
1307 		RETURN_THROWS();
1308 	}
1309 
1310 	DOM_GET_OBJ(elemp, id, xmlNodePtr, intern);
1311 
1312 	bool should_free_result = false;
1313 	const xmlChar *result = dom_get_attribute_ns(intern, elemp, uri, uri_len, name, &should_free_result);
1314 	if (result == NULL) {
1315 		RETURN_FALSE;
1316 	} else {
1317 		if (should_free_result) {
1318 			xmlFree(BAD_CAST result);
1319 		}
1320 		RETURN_TRUE;
1321 	}
1322 }
1323 /* }}} end dom_element_has_attribute_ns */
1324 
php_set_attribute_id(xmlAttrPtr attrp,bool is_id,php_libxml_ref_obj * document)1325 static void php_set_attribute_id(xmlAttrPtr attrp, bool is_id, php_libxml_ref_obj *document) /* {{{ */
1326 {
1327 	if (is_id && attrp->atype != XML_ATTRIBUTE_ID) {
1328 		attrp->atype = XML_ATTRIBUTE_ID;
1329 	} else if (!is_id && attrp->atype == XML_ATTRIBUTE_ID) {
1330 		xmlRemoveID(attrp->doc, attrp);
1331 		attrp->atype = 0;
1332 	}
1333 
1334 	dom_mark_ids_modified(document);
1335 }
1336 /* }}} */
1337 
1338 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-ElSetIdAttr
1339 Since: DOM Level 3
1340 */
PHP_METHOD(DOMElement,setIdAttribute)1341 PHP_METHOD(DOMElement, setIdAttribute)
1342 {
1343 	zval *id;
1344 	xmlNode *nodep;
1345 	xmlAttrPtr attrp;
1346 	dom_object *intern;
1347 	char *name;
1348 	size_t name_len;
1349 	bool is_id;
1350 
1351 	id = ZEND_THIS;
1352 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sb", &name, &name_len, &is_id) == FAILURE) {
1353 		RETURN_THROWS();
1354 	}
1355 
1356 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1357 
1358 	attrp = xmlHasNsProp(nodep, BAD_CAST name, NULL);
1359 	if (attrp == NULL || attrp->type == XML_ATTRIBUTE_DECL) {
1360 		php_dom_throw_error(NOT_FOUND_ERR, dom_get_strict_error(intern->document));
1361 	} else {
1362 		php_set_attribute_id(attrp, is_id, intern->document);
1363 	}
1364 }
1365 /* }}} end dom_element_set_id_attribute */
1366 
1367 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-ElSetIdAttrNS
1368 Since: DOM Level 3
1369 */
PHP_METHOD(DOMElement,setIdAttributeNS)1370 PHP_METHOD(DOMElement, setIdAttributeNS)
1371 {
1372 	zval *id;
1373 	xmlNodePtr elemp;
1374 	xmlAttrPtr attrp;
1375 	dom_object *intern;
1376 	size_t uri_len, name_len;
1377 	char *uri, *name;
1378 	bool is_id;
1379 
1380 	id = ZEND_THIS;
1381 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssb", &uri, &uri_len, &name, &name_len, &is_id) == FAILURE) {
1382 		RETURN_THROWS();
1383 	}
1384 
1385 	DOM_GET_OBJ(elemp, id, xmlNodePtr, intern);
1386 
1387 	attrp = xmlHasNsProp(elemp, BAD_CAST name, BAD_CAST uri);
1388 	if (attrp == NULL || attrp->type == XML_ATTRIBUTE_DECL) {
1389 		php_dom_throw_error(NOT_FOUND_ERR, dom_get_strict_error(intern->document));
1390 	} else {
1391 		php_set_attribute_id(attrp, is_id, intern->document);
1392 	}
1393 }
1394 /* }}} end dom_element_set_id_attribute_ns */
1395 
1396 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-ElSetIdAttrNode
1397 Since: DOM Level 3
1398 */
dom_element_set_id_attribute_node(INTERNAL_FUNCTION_PARAMETERS,zend_class_entry * attr_ce)1399 static void dom_element_set_id_attribute_node(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *attr_ce)
1400 {
1401 	zval *id, *node;
1402 	xmlNode *nodep;
1403 	xmlAttrPtr attrp;
1404 	dom_object *intern, *attrobj;
1405 	bool is_id;
1406 
1407 	id = ZEND_THIS;
1408 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "Ob", &node, attr_ce, &is_id) != SUCCESS) {
1409 		RETURN_THROWS();
1410 	}
1411 
1412 	DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1413 	DOM_GET_OBJ(attrp, node, xmlAttrPtr, attrobj);
1414 
1415 	if (attrp->parent != nodep) {
1416 		php_dom_throw_error(NOT_FOUND_ERR, dom_get_strict_error(intern->document));
1417 	} else {
1418 		php_set_attribute_id(attrp, is_id, intern->document);
1419 	}
1420 }
1421 
PHP_METHOD(DOMElement,setIdAttributeNode)1422 PHP_METHOD(DOMElement, setIdAttributeNode)
1423 {
1424 	dom_element_set_id_attribute_node(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_attr_class_entry);
1425 }
1426 
PHP_METHOD(Dom_Element,setIdAttributeNode)1427 PHP_METHOD(Dom_Element, setIdAttributeNode)
1428 {
1429 	dom_element_set_id_attribute_node(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_modern_attr_class_entry);
1430 }
1431 /* }}} end dom_element_set_id_attribute_node */
1432 
1433 /* {{{ URL:
1434 Since:
1435 */
PHP_METHOD(DOMElement,remove)1436 PHP_METHOD(DOMElement, remove)
1437 {
1438 	dom_object *intern;
1439 
1440 	if (zend_parse_parameters_none() == FAILURE) {
1441 		RETURN_THROWS();
1442 	}
1443 
1444 	DOM_GET_THIS_INTERN(intern);
1445 
1446 	dom_child_node_remove(intern);
1447 }
1448 /* }}} end DOMElement::remove */
1449 
PHP_METHOD(DOMElement,after)1450 PHP_METHOD(DOMElement, after)
1451 {
1452 	uint32_t argc = 0;
1453 	zval *args;
1454 	dom_object *intern;
1455 
1456 	ZEND_PARSE_PARAMETERS_START(0, -1)
1457 		Z_PARAM_VARIADIC('*', args, argc)
1458 	ZEND_PARSE_PARAMETERS_END();
1459 
1460 	DOM_GET_THIS_INTERN(intern);
1461 
1462 	dom_parent_node_after(intern, args, argc);
1463 }
1464 
PHP_METHOD(DOMElement,before)1465 PHP_METHOD(DOMElement, before)
1466 {
1467 	uint32_t argc = 0;
1468 	zval *args;
1469 	dom_object *intern;
1470 
1471 	ZEND_PARSE_PARAMETERS_START(0, -1)
1472 		Z_PARAM_VARIADIC('*', args, argc)
1473 	ZEND_PARSE_PARAMETERS_END();
1474 
1475 	DOM_GET_THIS_INTERN(intern);
1476 
1477 	dom_parent_node_before(intern, args, argc);
1478 }
1479 
1480 /* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-append
1481 Since: DOM Living Standard (DOM4)
1482 */
PHP_METHOD(DOMElement,append)1483 PHP_METHOD(DOMElement, append)
1484 {
1485 	uint32_t argc = 0;
1486 	zval *args;
1487 	dom_object *intern;
1488 
1489 	ZEND_PARSE_PARAMETERS_START(0, -1)
1490 		Z_PARAM_VARIADIC('*', args, argc)
1491 	ZEND_PARSE_PARAMETERS_END();
1492 
1493 	DOM_GET_THIS_INTERN(intern);
1494 
1495 	dom_parent_node_append(intern, args, argc);
1496 }
1497 /* }}} end DOMElement::append */
1498 
1499 /* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-prepend
1500 Since: DOM Living Standard (DOM4)
1501 */
PHP_METHOD(DOMElement,prepend)1502 PHP_METHOD(DOMElement, prepend)
1503 {
1504 	uint32_t argc = 0;
1505 	zval *args;
1506 	dom_object *intern;
1507 
1508 	ZEND_PARSE_PARAMETERS_START(0, -1)
1509 		Z_PARAM_VARIADIC('*', args, argc)
1510 	ZEND_PARSE_PARAMETERS_END();
1511 
1512 	DOM_GET_THIS_INTERN(intern);
1513 
1514 	dom_parent_node_prepend(intern, args, argc);
1515 }
1516 /* }}} end DOMElement::prepend */
1517 
1518 /* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
1519 Since: DOM Living Standard (DOM4)
1520 */
PHP_METHOD(DOMElement,replaceWith)1521 PHP_METHOD(DOMElement, replaceWith)
1522 {
1523 	uint32_t argc = 0;
1524 	zval *args;
1525 	dom_object *intern;
1526 
1527 	ZEND_PARSE_PARAMETERS_START(0, -1)
1528 		Z_PARAM_VARIADIC('*', args, argc)
1529 	ZEND_PARSE_PARAMETERS_END();
1530 
1531 	DOM_GET_THIS_INTERN(intern);
1532 
1533 	dom_child_replace_with(intern, args, argc);
1534 }
1535 /* }}} end DOMElement::prepend */
1536 
1537 /* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
1538 Since:
1539 */
PHP_METHOD(DOMElement,replaceChildren)1540 PHP_METHOD(DOMElement, replaceChildren)
1541 {
1542 	uint32_t argc = 0;
1543 	zval *args;
1544 	dom_object *intern;
1545 
1546 	ZEND_PARSE_PARAMETERS_START(0, -1)
1547 		Z_PARAM_VARIADIC('*', args, argc)
1548 	ZEND_PARSE_PARAMETERS_END();
1549 
1550 	DOM_GET_THIS_INTERN(intern);
1551 
1552 	dom_parent_node_replace_children(intern, args, argc);
1553 }
1554 /* }}} */
1555 
1556 #define INSERT_ADJACENT_RES_ADOPT_FAILED ((void*) -1)
1557 #define INSERT_ADJACENT_RES_SYNTAX_FAILED INSERT_ADJACENT_RES_ADOPT_FAILED
1558 #define INSERT_ADJACENT_RES_PRE_INSERT_FAILED ((void*) -2)
1559 
dom_insert_adjacent(const zend_string * where,xmlNodePtr thisp,dom_object * this_intern,xmlNodePtr otherp)1560 static xmlNodePtr dom_insert_adjacent(const zend_string *where, xmlNodePtr thisp, dom_object *this_intern, xmlNodePtr otherp)
1561 {
1562 	if (zend_string_equals_literal_ci(where, "beforebegin")) {
1563 		if (thisp->parent == NULL) {
1564 			return NULL;
1565 		}
1566 		if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
1567 			return INSERT_ADJACENT_RES_ADOPT_FAILED;
1568 		}
1569 		if (!php_dom_pre_insert(this_intern->document, otherp, thisp->parent, thisp)) {
1570 			return INSERT_ADJACENT_RES_PRE_INSERT_FAILED;
1571 		}
1572 	} else if (zend_string_equals_literal_ci(where, "afterbegin")) {
1573 		if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
1574 			return INSERT_ADJACENT_RES_ADOPT_FAILED;
1575 		}
1576 		if (!php_dom_pre_insert(this_intern->document, otherp, thisp, thisp->children)) {
1577 			return INSERT_ADJACENT_RES_PRE_INSERT_FAILED;
1578 		}
1579 	} else if (zend_string_equals_literal_ci(where, "beforeend")) {
1580 		if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
1581 			return INSERT_ADJACENT_RES_ADOPT_FAILED;
1582 		}
1583 		if (!php_dom_pre_insert(this_intern->document, otherp, thisp, NULL)) {
1584 			return INSERT_ADJACENT_RES_PRE_INSERT_FAILED;
1585 		}
1586 	} else if (zend_string_equals_literal_ci(where, "afterend")) {
1587 		if (thisp->parent == NULL) {
1588 			return NULL;
1589 		}
1590 		if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
1591 			return INSERT_ADJACENT_RES_ADOPT_FAILED;
1592 		}
1593 		if (!php_dom_pre_insert(this_intern->document, otherp, thisp->parent, thisp->next))  {
1594 			return INSERT_ADJACENT_RES_PRE_INSERT_FAILED;
1595 		}
1596 	} else {
1597 		php_dom_throw_error(SYNTAX_ERR, dom_get_strict_error(this_intern->document));
1598 		return INSERT_ADJACENT_RES_SYNTAX_FAILED;
1599 	}
1600 	return otherp;
1601 }
1602 
1603 /* {{{ URL: https://dom.spec.whatwg.org/#dom-element-insertadjacentelement
1604 Since:
1605 */
dom_element_insert_adjacent_element(INTERNAL_FUNCTION_PARAMETERS,const zend_string * where,zval * element_zval)1606 static void dom_element_insert_adjacent_element(INTERNAL_FUNCTION_PARAMETERS, const zend_string *where, zval *element_zval)
1607 {
1608 	zval *id;
1609 	xmlNodePtr thisp, otherp;
1610 	dom_object *this_intern, *other_intern;
1611 
1612 	DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, this_intern);
1613 	DOM_GET_OBJ(otherp, element_zval, xmlNodePtr, other_intern);
1614 
1615 	xmlNodePtr result = dom_insert_adjacent(where, thisp, this_intern, otherp);
1616 	if (result == NULL) {
1617 		RETURN_NULL();
1618 	} else if (result != INSERT_ADJACENT_RES_ADOPT_FAILED && result != INSERT_ADJACENT_RES_PRE_INSERT_FAILED) {
1619 		DOM_RET_OBJ(otherp, other_intern);
1620 	} else {
1621 		RETURN_THROWS();
1622 	}
1623 }
1624 
PHP_METHOD(DOMElement,insertAdjacentElement)1625 PHP_METHOD(DOMElement, insertAdjacentElement)
1626 {
1627 	zend_string *where;
1628 	zval *element_zval;
1629 
1630 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SO", &where, &element_zval, dom_element_class_entry) != SUCCESS) {
1631 		RETURN_THROWS();
1632 	}
1633 
1634 	dom_element_insert_adjacent_element(INTERNAL_FUNCTION_PARAM_PASSTHRU, where, element_zval);
1635 }
1636 
PHP_METHOD(Dom_Element,insertAdjacentElement)1637 PHP_METHOD(Dom_Element, insertAdjacentElement)
1638 {
1639 	zval *element_zval, *where_zv;
1640 
1641 	ZEND_PARSE_PARAMETERS_START(2, 2)
1642 		Z_PARAM_OBJECT_OF_CLASS(where_zv, dom_adjacent_position_class_entry)
1643 		Z_PARAM_OBJECT_OF_CLASS(element_zval, dom_modern_element_class_entry)
1644 	ZEND_PARSE_PARAMETERS_END();
1645 
1646 	const zend_string *where = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(where_zv)));
1647 	dom_element_insert_adjacent_element(INTERNAL_FUNCTION_PARAM_PASSTHRU, where, element_zval);
1648 }
1649 /* }}} end DOMElement::insertAdjacentElement */
1650 
1651 /* {{{ URL: https://dom.spec.whatwg.org/#dom-element-insertadjacenttext
1652 Since:
1653 */
dom_element_insert_adjacent_text(INTERNAL_FUNCTION_PARAMETERS,const zend_string * where,const zend_string * data)1654 static void dom_element_insert_adjacent_text(INTERNAL_FUNCTION_PARAMETERS, const zend_string *where, const zend_string *data)
1655 {
1656 	dom_object *this_intern;
1657 	zval *id;
1658 	xmlNodePtr thisp;
1659 
1660 	DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, this_intern);
1661 
1662 	if (UNEXPECTED(ZEND_SIZE_T_INT_OVFL(ZSTR_LEN(data)))) {
1663 		zend_argument_value_error(2, "is too long");
1664 		RETURN_THROWS();
1665 	}
1666 
1667 	xmlNodePtr otherp = xmlNewDocTextLen(thisp->doc, (const xmlChar *) ZSTR_VAL(data), ZSTR_LEN(data));
1668 	xmlNodePtr result = dom_insert_adjacent(where, thisp, this_intern, otherp);
1669 	if (result == NULL || result == INSERT_ADJACENT_RES_ADOPT_FAILED) {
1670 		xmlFreeNode(otherp);
1671 	}
1672 }
1673 
PHP_METHOD(DOMElement,insertAdjacentText)1674 PHP_METHOD(DOMElement, insertAdjacentText)
1675 {
1676 	zend_string *where, *data;
1677 
1678 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &where, &data) == FAILURE) {
1679 		RETURN_THROWS();
1680 	}
1681 
1682 	dom_element_insert_adjacent_text(INTERNAL_FUNCTION_PARAM_PASSTHRU, where, data);
1683 }
1684 
PHP_METHOD(Dom_Element,insertAdjacentText)1685 PHP_METHOD(Dom_Element, insertAdjacentText)
1686 {
1687 	zval *where_zv;
1688 	zend_string *data;
1689 
1690 	ZEND_PARSE_PARAMETERS_START(2, 2)
1691 		Z_PARAM_OBJECT_OF_CLASS(where_zv, dom_adjacent_position_class_entry)
1692 		Z_PARAM_STR(data)
1693 	ZEND_PARSE_PARAMETERS_END();
1694 
1695 	const zend_string *where = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(where_zv)));
1696 	dom_element_insert_adjacent_text(INTERNAL_FUNCTION_PARAM_PASSTHRU, where, data);
1697 }
1698 /* }}} end DOMElement::insertAdjacentText */
1699 
1700 /* {{{ URL: https://dom.spec.whatwg.org/#dom-element-toggleattribute
1701 Since:
1702 */
PHP_METHOD(DOMElement,toggleAttribute)1703 PHP_METHOD(DOMElement, toggleAttribute)
1704 {
1705 	char *qname, *qname_tmp = NULL;
1706 	size_t qname_length;
1707 	bool force, force_is_null = true;
1708 	xmlNodePtr thisp;
1709 	zval *id;
1710 	dom_object *intern;
1711 	bool retval;
1712 
1713 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|b!", &qname, &qname_length, &force, &force_is_null) == FAILURE) {
1714 		RETURN_THROWS();
1715 	}
1716 
1717 	DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, intern);
1718 
1719 	/* Step 1 */
1720 	if (xmlValidateName(BAD_CAST qname, 0) != 0) {
1721 		php_dom_throw_error(INVALID_CHARACTER_ERR, true);
1722 		RETURN_THROWS();
1723 	}
1724 
1725 	bool follow_spec = php_dom_follow_spec_intern(intern);
1726 
1727 	/* Step 2 */
1728 	if (thisp->doc != NULL && thisp->doc->type == XML_HTML_DOCUMENT_NODE
1729 		&& ((!follow_spec && thisp->ns == NULL) || (thisp->ns != NULL && xmlStrEqual(thisp->ns->href, BAD_CAST DOM_XHTML_NS_URI)))) {
1730 		qname_tmp = zend_str_tolower_dup_ex(qname, qname_length);
1731 		if (qname_tmp != NULL) {
1732 			qname = qname_tmp;
1733 		}
1734 	}
1735 
1736 	/* Step 3 */
1737 	xmlNodePtr attribute = dom_get_attribute_or_nsdecl(intern, thisp, BAD_CAST qname, qname_length);
1738 
1739 	/* Step 4 */
1740 	if (attribute == NULL) {
1741 		/* Step 4.1 */
1742 		if (force_is_null || force) {
1743 			if (follow_spec) {
1744 				xmlSetNsProp(thisp, NULL, BAD_CAST qname, NULL);
1745 			} else {
1746 				/* The behaviour for namespaces isn't defined by spec, but this is based on observing browers behaviour.
1747 				* It follows the same rules when you'd manually add an attribute using the other APIs. */
1748 				int len;
1749 				const xmlChar *split = xmlSplitQName3((const xmlChar *) qname, &len);
1750 				if (split == NULL || strncmp(qname, "xmlns:", len + 1 /* +1 for matching ':' too */) != 0) {
1751 					/* unqualified name, or qualified name with no xml namespace declaration */
1752 					dom_create_attribute(thisp, qname, "");
1753 				} else {
1754 					/* qualified name with xml namespace declaration */
1755 					xmlNewNs(thisp, (const xmlChar *) "", (const xmlChar *) (qname + len + 1));
1756 				}
1757 			}
1758 			retval = true;
1759 			goto out;
1760 		}
1761 		/* Step 4.2 */
1762 		retval = false;
1763 		goto out;
1764 	}
1765 
1766 	/* Step 5 */
1767 	if (force_is_null || !force) {
1768 		dom_remove_attribute(thisp, attribute);
1769 		retval = false;
1770 		goto out;
1771 	}
1772 
1773 	/* Step 6 */
1774 	retval = true;
1775 
1776 out:
1777 	if (qname_tmp) {
1778 		efree(qname_tmp);
1779 	}
1780 	RETURN_BOOL(retval);
1781 }
1782 /* }}} end DOMElement::prepend */
1783 
php_dom_dispatch_query_selector(INTERNAL_FUNCTION_PARAMETERS,bool all)1784 static void php_dom_dispatch_query_selector(INTERNAL_FUNCTION_PARAMETERS, bool all)
1785 {
1786 	zend_string *selectors_str;
1787 
1788 	ZEND_PARSE_PARAMETERS_START(1, 1)
1789 		Z_PARAM_STR(selectors_str)
1790 	ZEND_PARSE_PARAMETERS_END();
1791 
1792 	xmlNodePtr thisp;
1793 	dom_object *intern;
1794 	zval *id;
1795 	DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, intern);
1796 
1797 	if (all) {
1798 		dom_parent_node_query_selector_all(thisp, intern, return_value, selectors_str);
1799 	} else {
1800 		dom_parent_node_query_selector(thisp, intern, return_value, selectors_str);
1801 	}
1802 }
1803 
PHP_METHOD(Dom_Element,querySelector)1804 PHP_METHOD(Dom_Element, querySelector)
1805 {
1806 	php_dom_dispatch_query_selector(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1807 }
1808 
PHP_METHOD(Dom_Element,querySelectorAll)1809 PHP_METHOD(Dom_Element, querySelectorAll)
1810 {
1811 	php_dom_dispatch_query_selector(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1812 }
1813 
PHP_METHOD(Dom_Element,matches)1814 PHP_METHOD(Dom_Element, matches)
1815 {
1816 	zend_string *selectors_str;
1817 
1818 	ZEND_PARSE_PARAMETERS_START(1, 1)
1819 		Z_PARAM_STR(selectors_str)
1820 	ZEND_PARSE_PARAMETERS_END();
1821 
1822 	xmlNodePtr thisp;
1823 	dom_object *intern;
1824 	zval *id;
1825 	DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, intern);
1826 
1827 	dom_element_matches(thisp, intern, return_value, selectors_str);
1828 }
1829 
PHP_METHOD(Dom_Element,closest)1830 PHP_METHOD(Dom_Element, closest)
1831 {
1832 	zend_string *selectors_str;
1833 
1834 	ZEND_PARSE_PARAMETERS_START(1, 1)
1835 		Z_PARAM_STR(selectors_str)
1836 	ZEND_PARSE_PARAMETERS_END();
1837 
1838 	xmlNodePtr thisp;
1839 	dom_object *intern;
1840 	zval *id;
1841 	DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, intern);
1842 
1843 	dom_element_closest(thisp, intern, return_value, selectors_str);
1844 }
1845 
dom_modern_element_substituted_node_value_read(dom_object * obj,zval * retval)1846 zend_result dom_modern_element_substituted_node_value_read(dom_object *obj, zval *retval)
1847 {
1848 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
1849 
1850 	xmlChar *content = xmlNodeGetContent(nodep);
1851 
1852 	if (UNEXPECTED(content == NULL)) {
1853 		php_dom_throw_error(INVALID_STATE_ERR, true);
1854 		return FAILURE;
1855 	} else {
1856 		ZVAL_STRING(retval, (const char *) content);
1857 		xmlFree(content);
1858 	}
1859 
1860 	return SUCCESS;
1861 }
1862 
dom_modern_element_substituted_node_value_write(dom_object * obj,zval * newval)1863 zend_result dom_modern_element_substituted_node_value_write(dom_object *obj, zval *newval)
1864 {
1865 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
1866 
1867 	php_libxml_invalidate_node_list_cache(obj->document);
1868 	dom_remove_all_children(nodep);
1869 	xmlNodeSetContentLen(nodep, (xmlChar *) Z_STRVAL_P(newval), Z_STRLEN_P(newval));
1870 
1871 	return SUCCESS;
1872 }
1873 
dom_element_get_in_scope_namespace_info(php_dom_libxml_ns_mapper * ns_mapper,HashTable * result,xmlNodePtr nodep,dom_object * intern)1874 static void dom_element_get_in_scope_namespace_info(php_dom_libxml_ns_mapper *ns_mapper, HashTable *result, xmlNodePtr nodep, dom_object *intern)
1875 {
1876 	HashTable prefix_to_ns_table;
1877 	zend_hash_init(&prefix_to_ns_table, 0, NULL, NULL, false);
1878 	zend_hash_real_init_mixed(&prefix_to_ns_table);
1879 
1880 	/* https://www.w3.org/TR/1999/REC-xpath-19991116/#namespace-nodes */
1881 	for (const xmlNode *cur = nodep; cur != NULL; cur = cur->parent) {
1882 		if (cur->type == XML_ELEMENT_NODE) {
1883 			/* Find the last attribute */
1884 			const xmlAttr *last = NULL;
1885 			for (const xmlAttr *attr = cur->properties; attr != NULL; attr = attr->next) {
1886 				last = attr;
1887 			}
1888 
1889 			/* Reversed loop because the parent traversal is reversed as well,
1890 			 * this will keep the ordering consistent. */
1891 			for (const xmlAttr *attr = last; attr != NULL; attr = attr->prev) {
1892 				if (attr->ns != NULL && php_dom_ns_is_fast_ex(attr->ns, php_dom_ns_is_xmlns_magic_token)
1893 					&& attr->children != NULL && attr->children->content != NULL) {
1894 					const char *prefix = attr->ns->prefix == NULL ? NULL : (const char *) attr->name;
1895 					const char *key = prefix == NULL ? "" : prefix;
1896 					xmlNsPtr ns = php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, prefix, (const char *) attr->children->content);
1897 					/* NULL is a valid value for the sentinel */
1898 					zval zv;
1899 					ZVAL_PTR(&zv, ns);
1900 					zend_hash_str_add(&prefix_to_ns_table, key, strlen(key), &zv);
1901 				}
1902 			}
1903 		}
1904 	}
1905 
1906 	xmlNsPtr ns;
1907 	zend_string *prefix;
1908 	ZEND_HASH_MAP_REVERSE_FOREACH_STR_KEY_PTR(&prefix_to_ns_table, prefix, ns) {
1909 		if (ZSTR_LEN(prefix) == 0 && (ns == NULL || ns->href == NULL || *ns->href == '\0')) {
1910 			/* Exception: "the value of the xmlns attribute for the nearest such element is non-empty" */
1911 			continue;
1912 		}
1913 
1914 		zval zv;
1915 		object_init_ex(&zv, dom_namespace_info_class_entry);
1916 		zend_object *obj = Z_OBJ(zv);
1917 
1918 		if (ZSTR_LEN(prefix) != 0) {
1919 			ZVAL_STR_COPY(OBJ_PROP_NUM(obj, 0), prefix);
1920 		} else {
1921 			ZVAL_NULL(OBJ_PROP_NUM(obj, 0));
1922 		}
1923 
1924 		if (ns != NULL && ns->href != NULL && *ns->href != '\0') {
1925 			ZVAL_STRING(OBJ_PROP_NUM(obj, 1), (const char *) ns->href);
1926 		} else {
1927 			ZVAL_NULL(OBJ_PROP_NUM(obj, 1));
1928 		}
1929 
1930 		php_dom_create_object(nodep, OBJ_PROP_NUM(obj, 2), intern);
1931 
1932 		zend_hash_next_index_insert_new(result, &zv);
1933 	} ZEND_HASH_FOREACH_END();
1934 
1935 	zend_hash_destroy(&prefix_to_ns_table);
1936 }
1937 
PHP_METHOD(Dom_Element,getInScopeNamespaces)1938 PHP_METHOD(Dom_Element, getInScopeNamespaces)
1939 {
1940 	zval *id;
1941 	xmlNode *nodep;
1942 	dom_object *intern;
1943 
1944 	ZEND_PARSE_PARAMETERS_NONE();
1945 
1946 	DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
1947 
1948 	php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
1949 
1950 	array_init(return_value);
1951 	HashTable *result = Z_ARRVAL_P(return_value);
1952 
1953 	dom_element_get_in_scope_namespace_info(ns_mapper, result, nodep, intern);
1954 }
1955 
PHP_METHOD(Dom_Element,getDescendantNamespaces)1956 PHP_METHOD(Dom_Element, getDescendantNamespaces)
1957 {
1958 	zval *id;
1959 	xmlNode *nodep;
1960 	dom_object *intern;
1961 
1962 	ZEND_PARSE_PARAMETERS_NONE();
1963 
1964 	DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
1965 
1966 	php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
1967 
1968 	array_init(return_value);
1969 	HashTable *result = Z_ARRVAL_P(return_value);
1970 
1971 	dom_element_get_in_scope_namespace_info(ns_mapper, result, nodep, intern);
1972 
1973 	xmlNodePtr cur = nodep->children;
1974 	while (cur != NULL) {
1975 		if (cur->type == XML_ELEMENT_NODE) {
1976 			/* TODO: this could be more optimized by updating the same HashTable repeatedly
1977 			 * instead of recreating it on every node. */
1978 			dom_element_get_in_scope_namespace_info(ns_mapper, result, cur, intern);
1979 		}
1980 
1981 		cur = php_dom_next_in_tree_order(cur, nodep);
1982 	}
1983 }
1984 
PHP_METHOD(Dom_Element,rename)1985 PHP_METHOD(Dom_Element, rename)
1986 {
1987 	zend_string *namespace_uri, *qualified_name;
1988 	ZEND_PARSE_PARAMETERS_START(2, 2)
1989 		Z_PARAM_STR_OR_NULL(namespace_uri)
1990 		Z_PARAM_STR(qualified_name)
1991 	ZEND_PARSE_PARAMETERS_END();
1992 
1993 	zval *id;
1994 	dom_object *intern;
1995 	xmlNodePtr nodep;
1996 	DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
1997 
1998 	xmlChar *localname = NULL, *prefix = NULL;
1999 	int errorcode = dom_validate_and_extract(namespace_uri, qualified_name, &localname, &prefix);
2000 	if (UNEXPECTED(errorcode != 0)) {
2001 		php_dom_throw_error(errorcode, /* strict */ true);
2002 		goto cleanup;
2003 	}
2004 
2005 	if (nodep->type == XML_ATTRIBUTE_NODE) {
2006 		/* Check for duplicate attributes. */
2007 		xmlAttrPtr existing = xmlHasNsProp(nodep->parent, localname, namespace_uri && ZSTR_VAL(namespace_uri)[0] != '\0' ? BAD_CAST ZSTR_VAL(namespace_uri) : NULL);
2008 		if (existing != NULL && existing != (xmlAttrPtr) nodep) {
2009 			php_dom_throw_error_with_message(INVALID_MODIFICATION_ERR, "An attribute with the given name in the given namespace already exists", /* strict */ true);
2010 			goto cleanup;
2011 		}
2012 	} else {
2013 		ZEND_ASSERT(nodep->type == XML_ELEMENT_NODE);
2014 
2015 		/* Check for moving to or away from the HTML namespace. */
2016 		bool is_currently_html_ns = php_dom_ns_is_fast(nodep, php_dom_ns_is_html_magic_token);
2017 		bool will_be_html_ns = namespace_uri != NULL && zend_string_equals_literal(namespace_uri, DOM_XHTML_NS_URI);
2018 		if (is_currently_html_ns != will_be_html_ns) {
2019 			if (is_currently_html_ns) {
2020 				php_dom_throw_error_with_message(
2021 					INVALID_MODIFICATION_ERR,
2022 					"It is not possible to move an element out of the HTML namespace because the HTML namespace is tied to the HTMLElement class",
2023 					/* strict */ true
2024 				);
2025 			} else {
2026 				php_dom_throw_error_with_message(
2027 					INVALID_MODIFICATION_ERR,
2028 					"It is not possible to move an element into the HTML namespace because the HTML namespace is tied to the HTMLElement class",
2029 					/* strict */ true
2030 				);
2031 			}
2032 			goto cleanup;
2033 		}
2034 
2035 		/* If we currently have a template but the new element type won't be a template, then throw away the templated content. */
2036 		if (is_currently_html_ns && xmlStrEqual(nodep->name, BAD_CAST "template") && !xmlStrEqual(localname, BAD_CAST "template")) {
2037 			php_dom_throw_error_with_message(
2038 				INVALID_MODIFICATION_ERR,
2039 				"It is not possible to rename the template element because it hosts a document fragment",
2040 				/* strict */ true
2041 			);
2042 			goto cleanup;
2043 		}
2044 	}
2045 
2046 	php_libxml_invalidate_node_list_cache(intern->document);
2047 
2048 	php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
2049 
2050 	/* Update namespace uri + prefix by querying the namespace mapper */
2051 	/* prefix can be NULL here, but that is taken care of by the called APIs. */
2052 	nodep->ns = php_dom_libxml_ns_mapper_get_ns_raw_prefix_string(ns_mapper, prefix, xmlStrlen(prefix), namespace_uri);
2053 
2054 	/* Change the local name */
2055 	if (xmlDictOwns(nodep->doc->dict, nodep->name) != 1) {
2056 		xmlFree((xmlChar *) nodep->name);
2057 	}
2058 	const xmlChar *copy = xmlDictLookup(nodep->doc->dict, localname, -1);
2059 	if (copy != NULL) {
2060 		nodep->name = copy;
2061 	} else {
2062 		nodep->name = localname;
2063 		localname = NULL;
2064 	}
2065 
2066 cleanup:
2067 	xmlFree(localname);
2068 	xmlFree(prefix);
2069 }
2070 
2071 #endif
2072