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