xref: /php-src/ext/dom/php_dom.c (revision 47feb579)
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    |          Marcus Borger <helly@php.net>                               |
16    +----------------------------------------------------------------------+
17 */
18 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include "php.h"
24 #if defined(HAVE_LIBXML) && defined(HAVE_DOM)
25 #include "php_dom.h"
26 #include "nodelist.h"
27 #include "html_collection.h"
28 #include "namespace_compat.h"
29 #include "internal_helpers.h"
30 #include "php_dom_arginfo.h"
31 #include "dom_properties.h"
32 #include "zend_interfaces.h"
33 #include "lexbor/lexbor/core/types.h"
34 #include "lexbor/lexbor/core/lexbor.h"
35 
36 #include "ext/standard/info.h"
37 
38 /* {{{ class entries */
39 PHP_DOM_EXPORT zend_class_entry *dom_node_class_entry;
40 PHP_DOM_EXPORT zend_class_entry *dom_modern_node_class_entry;
41 PHP_DOM_EXPORT zend_class_entry *dom_domexception_class_entry;
42 PHP_DOM_EXPORT zend_class_entry *dom_parentnode_class_entry;
43 PHP_DOM_EXPORT zend_class_entry *dom_modern_parentnode_class_entry;
44 PHP_DOM_EXPORT zend_class_entry *dom_childnode_class_entry;
45 PHP_DOM_EXPORT zend_class_entry *dom_modern_childnode_class_entry;
46 PHP_DOM_EXPORT zend_class_entry *dom_domimplementation_class_entry;
47 PHP_DOM_EXPORT zend_class_entry *dom_modern_domimplementation_class_entry;
48 PHP_DOM_EXPORT zend_class_entry *dom_documentfragment_class_entry;
49 PHP_DOM_EXPORT zend_class_entry *dom_modern_documentfragment_class_entry;
50 PHP_DOM_EXPORT zend_class_entry *dom_document_class_entry;
51 PHP_DOM_EXPORT zend_class_entry *dom_html_document_class_entry;
52 PHP_DOM_EXPORT zend_class_entry *dom_xml_document_class_entry;
53 PHP_DOM_EXPORT zend_class_entry *dom_nodelist_class_entry;
54 PHP_DOM_EXPORT zend_class_entry *dom_modern_nodelist_class_entry;
55 PHP_DOM_EXPORT zend_class_entry *dom_namednodemap_class_entry;
56 PHP_DOM_EXPORT zend_class_entry *dom_modern_namednodemap_class_entry;
57 PHP_DOM_EXPORT zend_class_entry *dom_modern_dtd_namednodemap_class_entry;
58 PHP_DOM_EXPORT zend_class_entry *dom_html_collection_class_entry;
59 PHP_DOM_EXPORT zend_class_entry *dom_characterdata_class_entry;
60 PHP_DOM_EXPORT zend_class_entry *dom_modern_characterdata_class_entry;
61 PHP_DOM_EXPORT zend_class_entry *dom_attr_class_entry;
62 PHP_DOM_EXPORT zend_class_entry *dom_modern_attr_class_entry;
63 PHP_DOM_EXPORT zend_class_entry *dom_element_class_entry;
64 PHP_DOM_EXPORT zend_class_entry *dom_modern_element_class_entry;
65 PHP_DOM_EXPORT zend_class_entry *dom_text_class_entry;
66 PHP_DOM_EXPORT zend_class_entry *dom_modern_text_class_entry;
67 PHP_DOM_EXPORT zend_class_entry *dom_comment_class_entry;
68 PHP_DOM_EXPORT zend_class_entry *dom_modern_comment_class_entry;
69 PHP_DOM_EXPORT zend_class_entry *dom_cdatasection_class_entry;
70 PHP_DOM_EXPORT zend_class_entry *dom_modern_cdatasection_class_entry;
71 PHP_DOM_EXPORT zend_class_entry *dom_documenttype_class_entry;
72 PHP_DOM_EXPORT zend_class_entry *dom_modern_documenttype_class_entry;
73 PHP_DOM_EXPORT zend_class_entry *dom_notation_class_entry;
74 PHP_DOM_EXPORT zend_class_entry *dom_modern_notation_class_entry;
75 PHP_DOM_EXPORT zend_class_entry *dom_entity_class_entry;
76 PHP_DOM_EXPORT zend_class_entry *dom_modern_entity_class_entry;
77 PHP_DOM_EXPORT zend_class_entry *dom_entityreference_class_entry;
78 PHP_DOM_EXPORT zend_class_entry *dom_modern_entityreference_class_entry;
79 PHP_DOM_EXPORT zend_class_entry *dom_processinginstruction_class_entry;
80 PHP_DOM_EXPORT zend_class_entry *dom_modern_processinginstruction_class_entry;
81 PHP_DOM_EXPORT zend_class_entry *dom_abstract_base_document_class_entry;
82 #ifdef LIBXML_XPATH_ENABLED
83 PHP_DOM_EXPORT zend_class_entry *dom_xpath_class_entry;
84 PHP_DOM_EXPORT zend_class_entry *dom_modern_xpath_class_entry;
85 #endif
86 PHP_DOM_EXPORT zend_class_entry *dom_namespace_node_class_entry;
87 /* }}} */
88 
89 static zend_object_handlers dom_object_handlers;
90 static zend_object_handlers dom_nnodemap_object_handlers;
91 static zend_object_handlers dom_nodelist_object_handlers;
92 static zend_object_handlers dom_modern_nnodemap_object_handlers;
93 static zend_object_handlers dom_modern_nodelist_object_handlers;
94 static zend_object_handlers dom_html_collection_object_handlers;
95 static zend_object_handlers dom_object_namespace_node_handlers;
96 static zend_object_handlers dom_modern_domimplementation_object_handlers;
97 #ifdef LIBXML_XPATH_ENABLED
98 zend_object_handlers dom_xpath_object_handlers;
99 #endif
100 
101 static HashTable classes;
102 /* {{{ prop handler tables */
103 static HashTable dom_document_prop_handlers;
104 static HashTable dom_xml_document_prop_handlers;
105 static HashTable dom_abstract_base_document_prop_handlers;
106 static HashTable dom_documentfragment_prop_handlers;
107 static HashTable dom_modern_documentfragment_prop_handlers;
108 static HashTable dom_node_prop_handlers;
109 static HashTable dom_modern_node_prop_handlers;
110 static HashTable dom_nodelist_prop_handlers;
111 static HashTable dom_namednodemap_prop_handlers;
112 static HashTable dom_characterdata_prop_handlers;
113 static HashTable dom_modern_characterdata_prop_handlers;
114 static HashTable dom_attr_prop_handlers;
115 static HashTable dom_modern_attr_prop_handlers;
116 static HashTable dom_element_prop_handlers;
117 static HashTable dom_modern_element_prop_handlers;
118 static HashTable dom_modern_element_prop_handlers;
119 static HashTable dom_text_prop_handlers;
120 static HashTable dom_modern_text_prop_handlers;
121 static HashTable dom_documenttype_prop_handlers;
122 static HashTable dom_modern_documenttype_prop_handlers;
123 static HashTable dom_notation_prop_handlers;
124 static HashTable dom_modern_notation_prop_handlers;
125 static HashTable dom_entity_prop_handlers;
126 static HashTable dom_modern_entity_prop_handlers;
127 static HashTable dom_processinginstruction_prop_handlers;
128 static HashTable dom_modern_processinginstruction_prop_handlers;
129 static HashTable dom_namespace_node_prop_handlers;
130 #ifdef LIBXML_XPATH_ENABLED
131 static HashTable dom_xpath_prop_handlers;
132 #endif
133 /* }}} */
134 
135 static zend_object *dom_objects_namespace_node_new(zend_class_entry *class_type);
136 static void dom_object_namespace_node_free_storage(zend_object *object);
137 static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original);
138 
139 typedef zend_result (*dom_read_t)(dom_object *obj, zval *retval);
140 typedef zend_result (*dom_write_t)(dom_object *obj, zval *newval);
141 
142 typedef struct _dom_prop_handler {
143 	dom_read_t read_func;
144 	dom_write_t write_func;
145 } dom_prop_handler;
146 
147 /* {{{ int dom_node_is_read_only(xmlNodePtr node) */
dom_node_is_read_only(xmlNodePtr node)148 int dom_node_is_read_only(xmlNodePtr node) {
149 	switch (node->type) {
150 		case XML_ENTITY_REF_NODE:
151 		case XML_ENTITY_NODE:
152 		case XML_DOCUMENT_TYPE_NODE:
153 		case XML_NOTATION_NODE:
154 		case XML_DTD_NODE:
155 		case XML_ELEMENT_DECL:
156 		case XML_ATTRIBUTE_DECL:
157 		case XML_ENTITY_DECL:
158 		case XML_NAMESPACE_DECL:
159 			return SUCCESS;
160 			break;
161 		default:
162 			if (node->doc == NULL) {
163 				return SUCCESS;
164 			} else {
165 				return FAILURE;
166 			}
167 	}
168 }
169 /* }}} end dom_node_is_read_only */
170 
dom_node_children_valid(xmlNodePtr node)171 bool dom_node_children_valid(xmlNodePtr node) {
172 	switch (node->type) {
173 		case XML_DOCUMENT_TYPE_NODE:
174 		case XML_DTD_NODE:
175 		case XML_PI_NODE:
176 		case XML_COMMENT_NODE:
177 		case XML_TEXT_NODE:
178 		case XML_CDATA_SECTION_NODE:
179 		case XML_NOTATION_NODE:
180 			return false;
181 		default:
182 			return true;
183 	}
184 }
185 
186 static const libxml_doc_props default_doc_props = {
187 	.formatoutput = false,
188 	.validateonparse = false,
189 	.resolveexternals = false,
190 	.preservewhitespace = true,
191 	.substituteentities = false,
192 	.stricterror = true,
193 	.recover = false,
194 	.classmap = NULL,
195 };
196 
197 /* {{{ dom_get_doc_props() */
dom_get_doc_props(php_libxml_ref_obj * document)198 dom_doc_propsptr dom_get_doc_props(php_libxml_ref_obj *document)
199 {
200 	dom_doc_propsptr doc_props;
201 
202 	if (document && document->doc_props) {
203 		return document->doc_props;
204 	} else {
205 		doc_props = emalloc(sizeof(libxml_doc_props));
206 		memcpy(doc_props, &default_doc_props, sizeof(libxml_doc_props));
207 		if (document) {
208 			document->doc_props = doc_props;
209 		}
210 		return doc_props;
211 	}
212 }
213 /* }}} */
214 
dom_get_doc_props_read_only(const php_libxml_ref_obj * document)215 libxml_doc_props const* dom_get_doc_props_read_only(const php_libxml_ref_obj *document)
216 {
217 	if (document && document->doc_props) {
218 		return document->doc_props;
219 	} else {
220 		return &default_doc_props;
221 	}
222 }
223 
dom_copy_document_ref(php_libxml_ref_obj * source_doc,php_libxml_ref_obj * dest_doc)224 static void dom_copy_document_ref(php_libxml_ref_obj *source_doc, php_libxml_ref_obj *dest_doc)
225 {
226 	dom_doc_propsptr dest;
227 
228 	if (source_doc && dest_doc) {
229 
230 		libxml_doc_props const* source = dom_get_doc_props_read_only(source_doc);
231 		dest = dom_get_doc_props(dest_doc);
232 
233 		dest->formatoutput = source->formatoutput;
234 		dest->validateonparse = source->validateonparse;
235 		dest->resolveexternals = source->resolveexternals;
236 		dest->preservewhitespace = source->preservewhitespace;
237 		dest->substituteentities = source->substituteentities;
238 		dest->stricterror = source->stricterror;
239 		dest->recover = source->recover;
240 		if (source->classmap) {
241 			ALLOC_HASHTABLE(dest->classmap);
242 			zend_hash_init(dest->classmap, 0, NULL, NULL, false);
243 			zend_hash_copy(dest->classmap, source->classmap, NULL);
244 		}
245 
246 		dest_doc->class_type = source_doc->class_type;
247 	}
248 }
249 
dom_set_doc_classmap(php_libxml_ref_obj * document,zend_class_entry * basece,zend_class_entry * ce)250 void dom_set_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece, zend_class_entry *ce)
251 {
252 	dom_doc_propsptr doc_props;
253 
254 	if (document) {
255 		doc_props = dom_get_doc_props(document);
256 		if (doc_props->classmap == NULL) {
257 			if (ce == NULL) {
258 				return;
259 			}
260 			ALLOC_HASHTABLE(doc_props->classmap);
261 			zend_hash_init(doc_props->classmap, 0, NULL, NULL, false);
262 		}
263 		if (ce) {
264 			zend_hash_update_ptr(doc_props->classmap, basece->name, ce);
265 		} else {
266 			zend_hash_del(doc_props->classmap, basece->name);
267 		}
268 	}
269 }
270 
dom_get_doc_classmap(php_libxml_ref_obj * document,zend_class_entry * basece)271 zend_class_entry *dom_get_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece)
272 {
273 	if (document) {
274 		libxml_doc_props const* doc_props = dom_get_doc_props_read_only(document);
275 		if (doc_props->classmap) {
276 			zend_class_entry *ce = zend_hash_find_ptr(doc_props->classmap, basece->name);
277 			if (ce) {
278 				return ce;
279 			}
280 		}
281 	}
282 
283 	return basece;
284 }
285 /* }}} */
286 
287 /* {{{ dom_get_strict_error() */
dom_get_strict_error(php_libxml_ref_obj * document)288 bool dom_get_strict_error(php_libxml_ref_obj *document) {
289 	return dom_get_doc_props_read_only(document)->stricterror;
290 }
291 /* }}} */
292 
293 /* {{{ xmlNodePtr dom_object_get_node(dom_object *obj) */
dom_object_get_node(dom_object * obj)294 PHP_DOM_EXPORT xmlNodePtr dom_object_get_node(dom_object *obj)
295 {
296 	if (obj && obj->ptr != NULL) {
297 		return ((php_libxml_node_ptr *)obj->ptr)->node;
298 	} else {
299 		return NULL;
300 	}
301 }
302 /* }}} end dom_object_get_node */
303 
304 /* {{{ dom_object *php_dom_object_get_data(xmlNodePtr obj) */
php_dom_object_get_data(xmlNodePtr obj)305 PHP_DOM_EXPORT dom_object *php_dom_object_get_data(xmlNodePtr obj)
306 {
307 	if (obj && obj->_private != NULL) {
308 		return (dom_object *) ((php_libxml_node_ptr *) obj->_private)->_private;
309 	} else {
310 		return NULL;
311 	}
312 }
313 /* }}} end php_dom_object_get_data */
314 
dom_register_prop_handler(HashTable * prop_handler,const char * name,size_t name_len,const dom_prop_handler * hnd)315 static void dom_register_prop_handler(HashTable *prop_handler, const char *name, size_t name_len, const dom_prop_handler *hnd)
316 {
317 	zend_string *str = zend_string_init_interned(name, name_len, true);
318 	zend_hash_add_new_ptr(prop_handler, str, (void *) hnd);
319 	zend_string_release_ex(str, true);
320 }
321 
dom_overwrite_prop_handler(HashTable * prop_handler,const char * name,size_t name_len,const dom_prop_handler * hnd)322 static void dom_overwrite_prop_handler(HashTable *prop_handler, const char *name, size_t name_len, const dom_prop_handler *hnd)
323 {
324 	zend_hash_str_update_ptr(prop_handler, name, name_len, (void *) hnd);
325 }
326 
327 #define DOM_REGISTER_PROP_HANDLER(prop_handler, name, prop_read_func, prop_write_func) do { \
328 		static const dom_prop_handler hnd = {.read_func = prop_read_func, .write_func = prop_write_func}; \
329 		dom_register_prop_handler(prop_handler, "" name, sizeof("" name) - 1, &hnd); \
330 	} while (0)
331 
332 #define DOM_OVERWRITE_PROP_HANDLER(prop_handler, name, prop_read_func, prop_write_func) do { \
333 		static const dom_prop_handler hnd = {.read_func = prop_read_func, .write_func = prop_write_func}; \
334 		dom_overwrite_prop_handler(prop_handler, "" name, sizeof("" name) - 1, &hnd); \
335 	} while (0)
336 
dom_get_property_ptr_ptr(zend_object * object,zend_string * name,int type,void ** cache_slot)337 static zval *dom_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot)
338 {
339 	dom_object *obj = php_dom_obj_from_obj(object);
340 
341 	if (!obj->prop_handler || !zend_hash_exists(obj->prop_handler, name)) {
342 		return zend_std_get_property_ptr_ptr(object, name, type, cache_slot);
343 	}
344 
345 	return NULL;
346 }
347 
348 /* {{{ dom_read_property */
dom_read_property(zend_object * object,zend_string * name,int type,void ** cache_slot,zval * rv)349 zval *dom_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv)
350 {
351 	dom_object *obj = php_dom_obj_from_obj(object);
352 	zval *retval;
353 	dom_prop_handler *hnd = NULL;
354 
355 	if (obj->prop_handler != NULL) {
356 		hnd = zend_hash_find_ptr(obj->prop_handler, name);
357 	}
358 
359 	if (hnd) {
360 		int ret = hnd->read_func(obj, rv);
361 		if (ret == SUCCESS) {
362 			retval = rv;
363 		} else {
364 			retval = &EG(uninitialized_zval);
365 		}
366 	} else {
367 		retval = zend_std_read_property(object, name, type, cache_slot, rv);
368 	}
369 
370 	return retval;
371 }
372 /* }}} */
373 
dom_write_property(zend_object * object,zend_string * name,zval * value,void ** cache_slot)374 zval *dom_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot)
375 {
376 	dom_object *obj = php_dom_obj_from_obj(object);
377 	dom_prop_handler *hnd = NULL;
378 
379 	if (obj->prop_handler != NULL) {
380 		hnd = zend_hash_find_ptr(obj->prop_handler, name);
381 	}
382 
383 	if (hnd) {
384 		if (!hnd->write_func) {
385 			zend_throw_error(NULL, "Cannot modify readonly property %s::$%s", ZSTR_VAL(object->ce->name), ZSTR_VAL(name));
386 			return &EG(error_zval);
387 		}
388 
389 		zend_property_info *prop = zend_get_property_info(object->ce, name, /* silent */ true);
390 		ZEND_ASSERT(prop && ZEND_TYPE_IS_SET(prop->type));
391 		zval tmp;
392 		ZVAL_COPY(&tmp, value);
393 		if (!zend_verify_property_type(prop, &tmp, ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data)))) {
394 			zval_ptr_dtor(&tmp);
395 			return &EG(error_zval);
396 		}
397 		hnd->write_func(obj, &tmp);
398 		zval_ptr_dtor(&tmp);
399 
400 		return value;
401 	}
402 
403 	return zend_std_write_property(object, name, value, cache_slot);
404 }
405 
406 /* {{{ dom_property_exists */
dom_property_exists(zend_object * object,zend_string * name,int check_empty,void ** cache_slot)407 static int dom_property_exists(zend_object *object, zend_string *name, int check_empty, void **cache_slot)
408 {
409 	dom_object *obj = php_dom_obj_from_obj(object);
410 	dom_prop_handler *hnd = NULL;
411 	int retval = 0;
412 
413 	if (obj->prop_handler != NULL) {
414 		hnd = zend_hash_find_ptr(obj->prop_handler, name);
415 	}
416 	if (hnd) {
417 		zval tmp;
418 
419 		if (check_empty == 2) {
420 			retval = 1;
421 		} else if (hnd->read_func(obj, &tmp) == SUCCESS) {
422 			if (check_empty == 1) {
423 				retval = zend_is_true(&tmp);
424 			} else if (check_empty == 0) {
425 				retval = (Z_TYPE(tmp) != IS_NULL);
426 			}
427 			zval_ptr_dtor(&tmp);
428 		}
429 	} else {
430 		retval = zend_std_has_property(object, name, check_empty, cache_slot);
431 	}
432 
433 	return retval;
434 }
435 /* }}} */
436 
dom_get_debug_info_helper(zend_object * object,int * is_temp)437 static HashTable* dom_get_debug_info_helper(zend_object *object, int *is_temp) /* {{{ */
438 {
439 	dom_object			*obj = php_dom_obj_from_obj(object);
440 	HashTable			*debug_info,
441 						*prop_handlers = obj->prop_handler,
442 						*std_props;
443 	zend_string			*string_key;
444 	dom_prop_handler	*entry;
445 	zend_string         *object_str;
446 
447 	*is_temp = 1;
448 
449 	std_props = zend_std_get_properties(object);
450 	debug_info = zend_array_dup(std_props);
451 
452 	if (!prop_handlers) {
453 		return debug_info;
454 	}
455 
456 	object_str = ZSTR_INIT_LITERAL("(object value omitted)", false);
457 
458 	ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(prop_handlers, string_key, entry) {
459 		zval value;
460 
461 		ZEND_ASSERT(string_key != NULL);
462 
463 		if (entry->read_func(obj, &value) == FAILURE) {
464 			continue;
465 		}
466 
467 		if (Z_TYPE(value) == IS_OBJECT) {
468 			zval_ptr_dtor(&value);
469 			ZVAL_NEW_STR(&value, object_str);
470 			zend_string_addref(object_str);
471 		}
472 
473 		zend_hash_update(debug_info, string_key, &value);
474 	} ZEND_HASH_FOREACH_END();
475 
476 	zend_string_release_ex(object_str, false);
477 
478 	return debug_info;
479 }
480 /* }}} */
481 
dom_get_debug_info(zend_object * object,int * is_temp)482 static HashTable* dom_get_debug_info(zend_object *object, int *is_temp) /* {{{ */
483 {
484 	return dom_get_debug_info_helper(object, is_temp);
485 }
486 /* }}} */
487 
php_dom_export_node(zval * object)488 void *php_dom_export_node(zval *object) /* {{{ */
489 {
490 	php_libxml_node_object *intern;
491 	xmlNodePtr nodep = NULL;
492 
493 	intern = (php_libxml_node_object *) Z_DOMOBJ_P(object);
494 	if (intern->node) {
495 		nodep = intern->node->node;
496 	}
497 
498 	return nodep;
499 }
500 /* }}} */
501 
502 /* {{{ Get a simplexml_element object from dom to allow for processing */
dom_import_simplexml_common(INTERNAL_FUNCTION_PARAMETERS,php_libxml_class_type new_class)503 static void dom_import_simplexml_common(INTERNAL_FUNCTION_PARAMETERS, php_libxml_class_type new_class)
504 {
505 	zval *node;
506 	xmlNodePtr nodep = NULL;
507 	php_libxml_node_object *nodeobj;
508 
509 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "o", &node) == FAILURE) {
510 		RETURN_THROWS();
511 	}
512 
513 	nodeobj = (php_libxml_node_object *) ((char *) Z_OBJ_P(node) - Z_OBJ_HT_P(node)->offset);
514 	nodep = php_libxml_import_node(node);
515 
516 	if (nodep && nodeobj && nodeobj->document && (nodep->type == XML_ELEMENT_NODE || nodep->type == XML_ATTRIBUTE_NODE)) {
517 		php_libxml_class_type old_class_type = nodeobj->document->class_type;
518 		if (old_class_type != PHP_LIBXML_CLASS_UNSET && old_class_type != new_class) {
519 			if (new_class == PHP_LIBXML_CLASS_MODERN) {
520 				zend_argument_type_error(1, "must not be already imported as a DOMNode");
521 			} else {
522 				zend_argument_type_error(1, "must not be already imported as a DOM\\Node");
523 			}
524 			RETURN_THROWS();
525 		}
526 
527 		/* Lock the node class type to prevent creating multiple representations of the same node. */
528 		nodeobj->document->class_type = new_class;
529 
530 		if (old_class_type != PHP_LIBXML_CLASS_MODERN && new_class == PHP_LIBXML_CLASS_MODERN && nodep->doc != NULL) {
531 			dom_document_convert_to_modern(nodeobj->document, nodep->doc);
532 		}
533 
534 		DOM_RET_OBJ((xmlNodePtr) nodep, (dom_object *)nodeobj);
535 	} else {
536 		zend_argument_type_error(1, "is not a valid node type");
537 		RETURN_THROWS();
538 	}
539 }
540 
PHP_FUNCTION(dom_import_simplexml)541 PHP_FUNCTION(dom_import_simplexml)
542 {
543 	dom_import_simplexml_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_LIBXML_CLASS_LEGACY);
544 }
545 
PHP_FUNCTION(DOM_import_simplexml)546 PHP_FUNCTION(DOM_import_simplexml)
547 {
548 	dom_import_simplexml_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_LIBXML_CLASS_MODERN);
549 }
550 /* }}} */
551 
552 static dom_object* dom_objects_set_class(zend_class_entry *class_type);
553 
php_dom_update_document_after_clone(dom_object * original,xmlNodePtr original_node,dom_object * clone,xmlNodePtr cloned_node)554 void php_dom_update_document_after_clone(dom_object *original, xmlNodePtr original_node, dom_object *clone, xmlNodePtr cloned_node)
555 {
556 	dom_copy_document_ref(original->document, clone->document);
557 	/* Workaround libxml2 bug, see https://gitlab.gnome.org/GNOME/libxml2/-/commit/07920b4381873187c02df53fa9b5d44aff3a7041 */
558 #if LIBXML_VERSION < 20911
559 	if (original_node->type == XML_HTML_DOCUMENT_NODE) {
560 		cloned_node->type = XML_HTML_DOCUMENT_NODE;
561 	}
562 #endif
563 }
564 
dom_update_refcount_after_clone(dom_object * original,xmlNodePtr original_node,dom_object * clone,xmlNodePtr cloned_node)565 static void dom_update_refcount_after_clone(dom_object *original, xmlNodePtr original_node, dom_object *clone, xmlNodePtr cloned_node)
566 {
567 	/* If we cloned a document then we must create new doc proxy */
568 	if (cloned_node->doc == original_node->doc) {
569 		clone->document = original->document;
570 	}
571 	php_libxml_increment_doc_ref((php_libxml_node_object *)clone, cloned_node->doc);
572 	php_libxml_increment_node_ptr((php_libxml_node_object *)clone, cloned_node, (void *)clone);
573 	if (original->document != clone->document) {
574 		php_dom_update_document_after_clone(original, original_node, clone, cloned_node);
575 	}
576 }
577 
dom_objects_store_clone_obj(zend_object * zobject)578 static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */
579 {
580 	dom_object *intern = php_dom_obj_from_obj(zobject);
581 	dom_object *clone = dom_objects_set_class(intern->std.ce);
582 
583 	if (instanceof_function(intern->std.ce, dom_node_class_entry) || instanceof_function(intern->std.ce, dom_modern_node_class_entry)) {
584 		xmlNodePtr node = (xmlNodePtr)dom_object_get_node(intern);
585 		if (node != NULL) {
586 			php_dom_libxml_ns_mapper *ns_mapper = NULL;
587 			if (php_dom_follow_spec_intern(intern)) {
588 				if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) {
589 					ns_mapper = php_dom_libxml_ns_mapper_create();
590 				} else {
591 					ns_mapper = php_dom_get_ns_mapper(intern);
592 				}
593 			}
594 
595 			xmlNodePtr cloned_node = dom_clone_node(ns_mapper, node, node->doc, true);
596 			if (cloned_node != NULL) {
597 				dom_update_refcount_after_clone(intern, node, clone, cloned_node);
598 			}
599 			clone->document->private_data = php_dom_libxml_ns_mapper_header(ns_mapper);
600 		}
601 	}
602 
603 	zend_objects_clone_members(&clone->std, &intern->std);
604 
605 	return &clone->std;
606 }
607 /* }}} */
608 
dom_object_namespace_node_clone_obj(zend_object * zobject)609 static zend_object *dom_object_namespace_node_clone_obj(zend_object *zobject)
610 {
611 	dom_object_namespace_node *intern = php_dom_namespace_node_obj_from_obj(zobject);
612 	zend_object *clone = dom_objects_namespace_node_new(intern->dom.std.ce);
613 	dom_object_namespace_node *clone_intern = php_dom_namespace_node_obj_from_obj(clone);
614 
615 	xmlNodePtr original_node = dom_object_get_node(&intern->dom);
616 	ZEND_ASSERT(original_node->type == XML_NAMESPACE_DECL);
617 	xmlNodePtr cloned_node = php_dom_create_fake_namespace_decl_node_ptr(original_node->parent, original_node->ns);
618 
619 	if (intern->parent_intern) {
620 		clone_intern->parent_intern = intern->parent_intern;
621 		GC_ADDREF(&clone_intern->parent_intern->std);
622 	}
623 	dom_update_refcount_after_clone(&intern->dom, original_node, &clone_intern->dom, cloned_node);
624 
625 	zend_objects_clone_members(clone, &intern->dom.std);
626 	return clone;
627 }
628 
629 static const zend_module_dep dom_deps[] = {
630 	ZEND_MOD_REQUIRED("libxml")
631 	ZEND_MOD_CONFLICTS("domxml")
632 	ZEND_MOD_END
633 };
634 
635 zend_module_entry dom_module_entry = { /* {{{ */
636 	STANDARD_MODULE_HEADER_EX, NULL,
637 	dom_deps,
638 	"dom",
639 	ext_functions,
640 	PHP_MINIT(dom),
641 	PHP_MSHUTDOWN(dom),
642 	NULL,
643 	NULL,
644 	PHP_MINFO(dom),
645 	DOM_API_VERSION, /* Extension versionnumber */
646 	STANDARD_MODULE_PROPERTIES
647 };
648 /* }}} */
649 
650 #ifdef COMPILE_DL_DOM
651 ZEND_GET_MODULE(dom)
652 #endif
653 
654 void dom_objects_free_storage(zend_object *object);
655 void dom_nnodemap_objects_free_storage(zend_object *object);
656 static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
657 static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty);
658 static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
659 static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty);
660 static zval *dom_modern_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
661 static int dom_modern_nodemap_has_dimension(zend_object *object, zval *member, int check_empty);
662 static zend_object *dom_objects_store_clone_obj(zend_object *zobject);
663 
664 #ifdef LIBXML_XPATH_ENABLED
665 void dom_xpath_objects_free_storage(zend_object *object);
666 HashTable *dom_xpath_get_gc(zend_object *object, zval **table, int *n);
667 #endif
668 
dom_malloc(size_t size)669 static void *dom_malloc(size_t size) {
670 	return emalloc(size);
671 }
672 
dom_realloc(void * dst,size_t size)673 static void *dom_realloc(void *dst, size_t size) {
674 	return erealloc(dst, size);
675 }
676 
dom_calloc(size_t num,size_t size)677 static void *dom_calloc(size_t num, size_t size) {
678 	return ecalloc(num, size);
679 }
680 
dom_free(void * ptr)681 static void dom_free(void *ptr) {
682 	efree(ptr);
683 }
684 
685 /* {{{ PHP_MINIT_FUNCTION(dom) */
PHP_MINIT_FUNCTION(dom)686 PHP_MINIT_FUNCTION(dom)
687 {
688 	memcpy(&dom_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
689 	dom_object_handlers.offset = XtOffsetOf(dom_object, std);
690 	dom_object_handlers.free_obj = dom_objects_free_storage;
691 	dom_object_handlers.read_property = dom_read_property;
692 	dom_object_handlers.write_property = dom_write_property;
693 	dom_object_handlers.get_property_ptr_ptr = dom_get_property_ptr_ptr;
694 	dom_object_handlers.clone_obj = dom_objects_store_clone_obj;
695 	dom_object_handlers.has_property = dom_property_exists;
696 	dom_object_handlers.get_debug_info = dom_get_debug_info;
697 
698 	memcpy(&dom_modern_domimplementation_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
699 	/* The IDL has the [SameObject] constraint, which is incompatible with cloning because it imposes that there is only
700 	 * one instance per parent object. */
701 	dom_modern_domimplementation_object_handlers.clone_obj = NULL;
702 
703 	memcpy(&dom_nnodemap_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
704 	dom_nnodemap_object_handlers.free_obj = dom_nnodemap_objects_free_storage;
705 	dom_nnodemap_object_handlers.read_dimension = dom_nodemap_read_dimension;
706 	dom_nnodemap_object_handlers.has_dimension = dom_nodemap_has_dimension;
707 
708 	memcpy(&dom_nodelist_object_handlers, &dom_nnodemap_object_handlers, sizeof(zend_object_handlers));
709 	dom_nodelist_object_handlers.read_dimension = dom_nodelist_read_dimension;
710 	dom_nodelist_object_handlers.has_dimension = dom_nodelist_has_dimension;
711 
712 	memcpy(&dom_modern_nnodemap_object_handlers, &dom_nnodemap_object_handlers, sizeof(zend_object_handlers));
713 	dom_modern_nnodemap_object_handlers.read_dimension = dom_modern_nodemap_read_dimension;
714 	dom_modern_nnodemap_object_handlers.has_dimension = dom_modern_nodemap_has_dimension;
715 
716 	memcpy(&dom_modern_nodelist_object_handlers, &dom_nodelist_object_handlers, sizeof(zend_object_handlers));
717 	dom_modern_nodelist_object_handlers.read_dimension = dom_modern_nodelist_read_dimension;
718 	dom_modern_nodelist_object_handlers.has_dimension = dom_modern_nodelist_has_dimension;
719 
720 	memcpy(&dom_html_collection_object_handlers, &dom_modern_nodelist_object_handlers, sizeof(zend_object_handlers));
721 	dom_html_collection_object_handlers.read_dimension = dom_html_collection_read_dimension;
722 	dom_html_collection_object_handlers.has_dimension = dom_html_collection_has_dimension;
723 
724 	memcpy(&dom_object_namespace_node_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
725 	dom_object_namespace_node_handlers.offset = XtOffsetOf(dom_object_namespace_node, dom.std);
726 	dom_object_namespace_node_handlers.free_obj = dom_object_namespace_node_free_storage;
727 	dom_object_namespace_node_handlers.clone_obj = dom_object_namespace_node_clone_obj;
728 
729 	zend_hash_init(&classes, 0, NULL, NULL, true);
730 
731 	dom_domexception_class_entry = register_class_DOMException(zend_ce_exception);
732 
733 	dom_parentnode_class_entry = register_class_DOMParentNode();
734 	dom_modern_parentnode_class_entry = register_class_DOM_ParentNode();
735 	dom_childnode_class_entry = register_class_DOMChildNode();
736 	dom_modern_childnode_class_entry = register_class_DOM_ChildNode();
737 
738 	dom_domimplementation_class_entry = register_class_DOMImplementation();
739 	dom_domimplementation_class_entry->create_object = dom_objects_new;
740 	dom_domimplementation_class_entry->default_object_handlers = &dom_object_handlers;
741 
742 	dom_modern_domimplementation_class_entry = register_class_DOM_Implementation();
743 	dom_modern_domimplementation_class_entry->create_object = dom_objects_new;
744 	dom_modern_domimplementation_class_entry->default_object_handlers = &dom_modern_domimplementation_object_handlers;
745 
746 	dom_node_class_entry = register_class_DOMNode();
747 	dom_node_class_entry->create_object = dom_objects_new;
748 	dom_node_class_entry->default_object_handlers = &dom_object_handlers;
749 
750 	zend_hash_init(&dom_node_prop_handlers, 0, NULL, NULL, true);
751 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "nodeName", dom_node_node_name_read, NULL);
752 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "nodeValue", dom_node_node_value_read, dom_node_node_value_write);
753 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "nodeType", dom_node_node_type_read, NULL);
754 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "parentNode", dom_node_parent_node_read, NULL);
755 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "parentElement", dom_node_parent_element_read, NULL);
756 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "childNodes", dom_node_child_nodes_read, NULL);
757 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "firstChild", dom_node_first_child_read, NULL);
758 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "lastChild", dom_node_last_child_read, NULL);
759 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "previousSibling", dom_node_previous_sibling_read, NULL);
760 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "nextSibling", dom_node_next_sibling_read, NULL);
761 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "attributes", dom_node_attributes_read, NULL);
762 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "isConnected", dom_node_is_connected_read, NULL);
763 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "ownerDocument", dom_node_owner_document_read, NULL);
764 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "namespaceURI", dom_node_namespace_uri_read, NULL);
765 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "prefix", dom_node_prefix_read, dom_node_prefix_write);
766 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "localName", dom_node_local_name_read, NULL);
767 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "baseURI", dom_node_base_uri_read, NULL);
768 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write);
769 	zend_hash_add_new_ptr(&classes, dom_node_class_entry->name, &dom_node_prop_handlers);
770 
771 	dom_modern_node_class_entry = register_class_DOM_Node();
772 	dom_modern_node_class_entry->create_object = dom_objects_new;
773 	dom_modern_node_class_entry->default_object_handlers = &dom_object_handlers;
774 
775 	zend_hash_init(&dom_modern_node_prop_handlers, 0, NULL, NULL, true);
776 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "nodeType", dom_node_node_type_read, NULL);
777 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "nodeName", dom_node_node_name_read, NULL);
778 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "baseURI", dom_node_base_uri_read, NULL);
779 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "isConnected", dom_node_is_connected_read, NULL);
780 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "ownerDocument", dom_node_owner_document_read, NULL);
781 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "parentNode", dom_node_parent_node_read, NULL);
782 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "parentElement", dom_node_parent_element_read, NULL);
783 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "childNodes", dom_node_child_nodes_read, NULL);
784 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "firstChild", dom_node_first_child_read, NULL);
785 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "lastChild", dom_node_last_child_read, NULL);
786 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "previousSibling", dom_node_previous_sibling_read, NULL);
787 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "nextSibling", dom_node_next_sibling_read, NULL);
788 	/* We will set-up the setter for the derived classes afterwards on a class-by-class basis. */
789 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "nodeValue", dom_node_node_value_read, NULL);
790 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "textContent", dom_node_text_content_read, NULL);
791 	zend_hash_add_new_ptr(&classes, dom_modern_node_class_entry->name, &dom_modern_node_prop_handlers);
792 
793 	dom_namespace_node_class_entry = register_class_DOMNameSpaceNode();
794 	dom_namespace_node_class_entry->create_object = dom_objects_namespace_node_new;
795 	dom_namespace_node_class_entry->default_object_handlers = &dom_object_namespace_node_handlers;
796 
797 	zend_hash_init(&dom_namespace_node_prop_handlers, 0, NULL, NULL, true);
798 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "nodeName", dom_node_node_name_read, NULL);
799 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "nodeValue", dom_node_node_value_read, NULL);
800 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "nodeType", dom_node_node_type_read, NULL);
801 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "prefix", dom_node_prefix_read, NULL);
802 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "localName", dom_node_local_name_read, NULL);
803 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "namespaceURI", dom_node_namespace_uri_read, NULL);
804 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "isConnected", dom_node_is_connected_read, NULL);
805 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "ownerDocument", dom_node_owner_document_read, NULL);
806 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "parentNode", dom_node_parent_node_read, NULL);
807 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "parentElement", dom_node_parent_element_read, NULL);
808 	zend_hash_add_new_ptr(&classes, dom_namespace_node_class_entry->name, &dom_namespace_node_prop_handlers);
809 
810 	dom_documentfragment_class_entry = register_class_DOMDocumentFragment(dom_node_class_entry, dom_parentnode_class_entry);
811 	dom_documentfragment_class_entry->create_object = dom_objects_new;
812 	dom_documentfragment_class_entry->default_object_handlers = &dom_object_handlers;
813 	zend_hash_init(&dom_documentfragment_prop_handlers, 0, NULL, NULL, true);
814 
815 	DOM_REGISTER_PROP_HANDLER(&dom_documentfragment_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
816 	DOM_REGISTER_PROP_HANDLER(&dom_documentfragment_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
817 	DOM_REGISTER_PROP_HANDLER(&dom_documentfragment_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
818 	zend_hash_merge(&dom_documentfragment_prop_handlers, &dom_node_prop_handlers, NULL, false);
819 	zend_hash_add_new_ptr(&classes, dom_documentfragment_class_entry->name, &dom_documentfragment_prop_handlers);
820 
821 	dom_modern_documentfragment_class_entry = register_class_DOM_DocumentFragment(dom_modern_node_class_entry, dom_modern_parentnode_class_entry);
822 	dom_modern_documentfragment_class_entry->create_object = dom_objects_new;
823 	dom_modern_documentfragment_class_entry->default_object_handlers = &dom_object_handlers;
824 	zend_hash_init(&dom_modern_documentfragment_prop_handlers, 0, NULL, NULL, true);
825 
826 	DOM_REGISTER_PROP_HANDLER(&dom_modern_documentfragment_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
827 	DOM_REGISTER_PROP_HANDLER(&dom_modern_documentfragment_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
828 	DOM_REGISTER_PROP_HANDLER(&dom_modern_documentfragment_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
829 	zend_hash_merge(&dom_modern_documentfragment_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
830 	DOM_OVERWRITE_PROP_HANDLER(&dom_modern_documentfragment_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write);
831 	zend_hash_add_new_ptr(&classes, dom_modern_documentfragment_class_entry->name, &dom_modern_documentfragment_prop_handlers);
832 
833 	dom_abstract_base_document_class_entry = register_class_DOM_Document(dom_modern_node_class_entry, dom_modern_parentnode_class_entry);
834 	dom_abstract_base_document_class_entry->default_object_handlers = &dom_object_handlers;
835 	zend_hash_init(&dom_abstract_base_document_prop_handlers, 0, NULL, NULL, true);
836 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "implementation", dom_modern_document_implementation_read, NULL);
837 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "URL", dom_document_document_uri_read, dom_document_document_uri_write);
838 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "documentURI", dom_document_document_uri_read, dom_document_document_uri_write);
839 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "characterSet", dom_document_encoding_read, dom_html_document_encoding_write);
840 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "charset", dom_document_encoding_read, dom_html_document_encoding_write);
841 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "inputEncoding", dom_document_encoding_read, dom_html_document_encoding_write);
842 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "doctype", dom_document_doctype_read, NULL);
843 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "documentElement", dom_document_document_element_read, NULL);
844 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
845 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
846 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
847 	zend_hash_merge(&dom_abstract_base_document_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
848 	/* No need to register in &classes because this is an abstract class handler. */
849 
850 	dom_document_class_entry = register_class_DOMDocument(dom_node_class_entry, dom_parentnode_class_entry);
851 	dom_document_class_entry->create_object = dom_objects_new;
852 	dom_document_class_entry->default_object_handlers = &dom_object_handlers;
853 	zend_hash_init(&dom_document_prop_handlers, 0, NULL, NULL, true);
854 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "doctype", dom_document_doctype_read, NULL);
855 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "implementation", dom_document_implementation_read, NULL);
856 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "documentElement", dom_document_document_element_read, NULL);
857 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "actualEncoding", dom_document_encoding_read, NULL);
858 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "encoding", dom_document_encoding_read, dom_document_encoding_write);
859 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "xmlEncoding", dom_document_encoding_read, NULL);
860 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "standalone", dom_document_standalone_read, dom_document_standalone_write);
861 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "xmlStandalone", dom_document_standalone_read, dom_document_standalone_write);
862 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "version", dom_document_version_read, dom_document_version_write);
863 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "xmlVersion", dom_document_version_read, dom_document_version_write);
864 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "strictErrorChecking", dom_document_strict_error_checking_read, dom_document_strict_error_checking_write);
865 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "documentURI", dom_document_document_uri_read, dom_document_document_uri_write);
866 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "config", dom_document_config_read, NULL);
867 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "formatOutput", dom_document_format_output_read, dom_document_format_output_write);
868 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "validateOnParse", dom_document_validate_on_parse_read, dom_document_validate_on_parse_write);
869 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "resolveExternals", dom_document_resolve_externals_read, dom_document_resolve_externals_write);
870 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "preserveWhiteSpace", dom_document_preserve_whitespace_read, dom_document_preserve_whitespace_write);
871 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "recover", dom_document_recover_read, dom_document_recover_write);
872 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "substituteEntities", dom_document_substitue_entities_read, dom_document_substitue_entities_write);
873 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
874 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
875 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
876 
877 	zend_hash_merge(&dom_document_prop_handlers, &dom_node_prop_handlers, NULL, false);
878 	zend_hash_add_new_ptr(&classes, dom_document_class_entry->name, &dom_document_prop_handlers);
879 
880 	dom_html_document_class_entry = register_class_DOM_HTMLDocument(dom_abstract_base_document_class_entry);
881 	dom_html_document_class_entry->default_object_handlers = &dom_object_handlers;
882 	zend_hash_add_new_ptr(&classes, dom_html_document_class_entry->name, &dom_abstract_base_document_prop_handlers);
883 
884 	dom_xml_document_class_entry = register_class_DOM_XMLDocument(dom_abstract_base_document_class_entry);
885 	dom_xml_document_class_entry->default_object_handlers = &dom_object_handlers;
886 	zend_hash_init(&dom_xml_document_prop_handlers, 0, NULL, NULL, true);
887 	DOM_REGISTER_PROP_HANDLER(&dom_xml_document_prop_handlers, "xmlEncoding", dom_document_encoding_read, NULL);
888 	DOM_REGISTER_PROP_HANDLER(&dom_xml_document_prop_handlers, "xmlStandalone", dom_document_standalone_read, dom_document_standalone_write);
889 	DOM_REGISTER_PROP_HANDLER(&dom_xml_document_prop_handlers, "xmlVersion", dom_document_version_read, dom_document_version_write);
890 	DOM_REGISTER_PROP_HANDLER(&dom_xml_document_prop_handlers, "formatOutput", dom_document_format_output_read, dom_document_format_output_write);
891 
892 	zend_hash_merge(&dom_xml_document_prop_handlers, &dom_abstract_base_document_prop_handlers, NULL, false);
893 	zend_hash_add_new_ptr(&classes, dom_xml_document_class_entry->name, &dom_xml_document_prop_handlers);
894 
895 	dom_nodelist_class_entry = register_class_DOMNodeList(zend_ce_aggregate, zend_ce_countable);
896 	dom_nodelist_class_entry->create_object = dom_nnodemap_objects_new;
897 	dom_nodelist_class_entry->default_object_handlers = &dom_nodelist_object_handlers;
898 	dom_nodelist_class_entry->get_iterator = php_dom_get_iterator;
899 
900 	zend_hash_init(&dom_nodelist_prop_handlers, 0, NULL, NULL, true);
901 	DOM_REGISTER_PROP_HANDLER(&dom_nodelist_prop_handlers, "length", dom_nodelist_length_read, NULL);
902 	zend_hash_add_new_ptr(&classes, dom_nodelist_class_entry->name, &dom_nodelist_prop_handlers);
903 
904 	dom_modern_nodelist_class_entry = register_class_DOM_NodeList(zend_ce_aggregate, zend_ce_countable);
905 	dom_modern_nodelist_class_entry->create_object = dom_nnodemap_objects_new;
906 	dom_modern_nodelist_class_entry->default_object_handlers = &dom_modern_nodelist_object_handlers;
907 	dom_modern_nodelist_class_entry->get_iterator = php_dom_get_iterator;
908 
909 	zend_hash_add_new_ptr(&classes, dom_modern_nodelist_class_entry->name, &dom_nodelist_prop_handlers);
910 
911 	dom_namednodemap_class_entry = register_class_DOMNamedNodeMap(zend_ce_aggregate, zend_ce_countable);
912 	dom_namednodemap_class_entry->create_object = dom_nnodemap_objects_new;
913 	dom_namednodemap_class_entry->default_object_handlers = &dom_nnodemap_object_handlers;
914 	dom_namednodemap_class_entry->get_iterator = php_dom_get_iterator;
915 
916 	zend_hash_init(&dom_namednodemap_prop_handlers, 0, NULL, NULL, true);
917 	DOM_REGISTER_PROP_HANDLER(&dom_namednodemap_prop_handlers, "length", dom_namednodemap_length_read, NULL);
918 	zend_hash_add_new_ptr(&classes, dom_namednodemap_class_entry->name, &dom_namednodemap_prop_handlers);
919 
920 	dom_modern_namednodemap_class_entry = register_class_DOM_NamedNodeMap(zend_ce_aggregate, zend_ce_countable);
921 	dom_modern_namednodemap_class_entry->create_object = dom_nnodemap_objects_new;
922 	dom_modern_namednodemap_class_entry->default_object_handlers = &dom_modern_nnodemap_object_handlers;
923 	dom_modern_namednodemap_class_entry->get_iterator = php_dom_get_iterator;
924 
925 	zend_hash_add_new_ptr(&classes, dom_modern_namednodemap_class_entry->name, &dom_namednodemap_prop_handlers);
926 
927 	dom_modern_dtd_namednodemap_class_entry = register_class_DOM_DTDNamedNodeMap(zend_ce_aggregate, zend_ce_countable);
928 	dom_modern_dtd_namednodemap_class_entry->create_object = dom_nnodemap_objects_new;
929 	dom_modern_dtd_namednodemap_class_entry->default_object_handlers = &dom_modern_nnodemap_object_handlers;
930 	dom_modern_dtd_namednodemap_class_entry->get_iterator = php_dom_get_iterator;
931 
932 	zend_hash_add_new_ptr(&classes, dom_modern_dtd_namednodemap_class_entry->name, &dom_namednodemap_prop_handlers);
933 
934 	dom_html_collection_class_entry = register_class_DOM_HTMLCollection(zend_ce_aggregate, zend_ce_countable);
935 	dom_html_collection_class_entry->create_object = dom_nnodemap_objects_new;
936 	dom_html_collection_class_entry->default_object_handlers = &dom_html_collection_object_handlers;
937 	dom_html_collection_class_entry->get_iterator = php_dom_get_iterator;
938 
939 	zend_hash_add_new_ptr(&classes, dom_html_collection_class_entry->name, &dom_nodelist_prop_handlers);
940 
941 	dom_characterdata_class_entry = register_class_DOMCharacterData(dom_node_class_entry, dom_childnode_class_entry);
942 	dom_characterdata_class_entry->create_object = dom_objects_new;
943 	dom_characterdata_class_entry->default_object_handlers = &dom_object_handlers;
944 
945 	zend_hash_init(&dom_characterdata_prop_handlers, 0, NULL, NULL, true);
946 	DOM_REGISTER_PROP_HANDLER(&dom_characterdata_prop_handlers, "data", dom_characterdata_data_read, dom_characterdata_data_write);
947 	DOM_REGISTER_PROP_HANDLER(&dom_characterdata_prop_handlers, "length", dom_characterdata_length_read, NULL);
948 	DOM_REGISTER_PROP_HANDLER(&dom_characterdata_prop_handlers, "previousElementSibling", dom_node_previous_element_sibling_read, NULL);
949 	DOM_REGISTER_PROP_HANDLER(&dom_characterdata_prop_handlers, "nextElementSibling", dom_node_next_element_sibling_read, NULL);
950 	zend_hash_merge(&dom_characterdata_prop_handlers, &dom_node_prop_handlers, NULL, false);
951 	zend_hash_add_new_ptr(&classes, dom_characterdata_class_entry->name, &dom_characterdata_prop_handlers);
952 
953 	dom_modern_characterdata_class_entry = register_class_DOM_CharacterData(dom_modern_node_class_entry, dom_modern_childnode_class_entry);
954 	dom_modern_characterdata_class_entry->create_object = dom_objects_new;
955 	dom_modern_characterdata_class_entry->default_object_handlers = &dom_object_handlers;
956 
957 	zend_hash_init(&dom_modern_characterdata_prop_handlers, 0, NULL, NULL, true);
958 	DOM_REGISTER_PROP_HANDLER(&dom_modern_characterdata_prop_handlers, "data", dom_characterdata_data_read, dom_characterdata_data_write);
959 	DOM_REGISTER_PROP_HANDLER(&dom_modern_characterdata_prop_handlers, "length", dom_characterdata_length_read, NULL);
960 	DOM_REGISTER_PROP_HANDLER(&dom_modern_characterdata_prop_handlers, "previousElementSibling", dom_node_previous_element_sibling_read, NULL);
961 	DOM_REGISTER_PROP_HANDLER(&dom_modern_characterdata_prop_handlers, "nextElementSibling", dom_node_next_element_sibling_read, NULL);
962 	zend_hash_merge(&dom_modern_characterdata_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
963 	DOM_OVERWRITE_PROP_HANDLER(&dom_modern_characterdata_prop_handlers, "nodeValue", dom_node_node_value_read, dom_node_node_value_write);
964 	DOM_OVERWRITE_PROP_HANDLER(&dom_modern_characterdata_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write);
965 	zend_hash_add_new_ptr(&classes, dom_modern_characterdata_class_entry->name, &dom_modern_characterdata_prop_handlers);
966 
967 	dom_attr_class_entry = register_class_DOMAttr(dom_node_class_entry);
968 	dom_attr_class_entry->create_object = dom_objects_new;
969 	dom_attr_class_entry->default_object_handlers = &dom_object_handlers;
970 
971 	zend_hash_init(&dom_attr_prop_handlers, 0, NULL, NULL, true);
972 	DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "name", dom_attr_name_read, NULL);
973 	DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "specified", dom_attr_specified_read, NULL);
974 	DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "value", dom_attr_value_read, dom_attr_value_write);
975 	DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "ownerElement", dom_attr_owner_element_read, NULL);
976 	DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "schemaTypeInfo", dom_attr_schema_type_info_read, NULL);
977 	zend_hash_merge(&dom_attr_prop_handlers, &dom_node_prop_handlers, NULL, false);
978 	zend_hash_add_new_ptr(&classes, dom_attr_class_entry->name, &dom_attr_prop_handlers);
979 
980 	dom_modern_attr_class_entry = register_class_DOM_Attr(dom_modern_node_class_entry);
981 	dom_modern_attr_class_entry->create_object = dom_objects_new;
982 	dom_modern_attr_class_entry->default_object_handlers = &dom_object_handlers;
983 
984 	zend_hash_init(&dom_modern_attr_prop_handlers, 0, NULL, NULL, true);
985 	DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "namespaceURI", dom_node_namespace_uri_read, NULL);
986 	DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "prefix", dom_modern_node_prefix_read, NULL);
987 	DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "localName", dom_node_local_name_read, NULL);
988 	DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "name", dom_attr_name_read, NULL);
989 	DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "value", dom_attr_value_read, dom_attr_value_write);
990 	DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "ownerElement", dom_attr_owner_element_read, NULL);
991 	DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "specified", dom_attr_specified_read, NULL);
992 	zend_hash_merge(&dom_modern_attr_prop_handlers, &dom_node_prop_handlers, NULL, false);
993 	DOM_OVERWRITE_PROP_HANDLER(&dom_modern_attr_prop_handlers, "nodeValue", dom_node_node_value_read, dom_node_node_value_write);
994 	DOM_OVERWRITE_PROP_HANDLER(&dom_modern_attr_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write);
995 	zend_hash_add_new_ptr(&classes, dom_modern_attr_class_entry->name, &dom_modern_attr_prop_handlers);
996 
997 	dom_element_class_entry = register_class_DOMElement(dom_node_class_entry, dom_parentnode_class_entry, dom_childnode_class_entry);
998 	dom_element_class_entry->create_object = dom_objects_new;
999 	dom_element_class_entry->default_object_handlers = &dom_object_handlers;
1000 
1001 	zend_hash_init(&dom_element_prop_handlers, 0, NULL, NULL, true);
1002 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "tagName", dom_element_tag_name_read, NULL);
1003 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "className", dom_element_class_name_read, dom_element_class_name_write);
1004 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "id", dom_element_id_read, dom_element_id_write);
1005 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "schemaTypeInfo", dom_element_schema_type_info_read, NULL);
1006 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
1007 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
1008 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
1009 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "previousElementSibling", dom_node_previous_element_sibling_read, NULL);
1010 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "nextElementSibling", dom_node_next_element_sibling_read, NULL);
1011 	zend_hash_merge(&dom_element_prop_handlers, &dom_node_prop_handlers, NULL, false);
1012 	zend_hash_add_new_ptr(&classes, dom_element_class_entry->name, &dom_element_prop_handlers);
1013 
1014 	dom_modern_element_class_entry = register_class_DOM_Element(dom_modern_node_class_entry, dom_modern_parentnode_class_entry, dom_modern_childnode_class_entry);
1015 	dom_modern_element_class_entry->create_object = dom_objects_new;
1016 	dom_modern_element_class_entry->default_object_handlers = &dom_object_handlers;
1017 
1018 	zend_hash_init(&dom_modern_element_prop_handlers, 0, NULL, NULL, true);
1019 	DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "namespaceURI", dom_node_namespace_uri_read, NULL);
1020 	DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "prefix", dom_modern_node_prefix_read, NULL);
1021 	DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "localName", dom_node_local_name_read, NULL);
1022 	DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "tagName", dom_element_tag_name_read, NULL);
1023 	DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "id", dom_element_id_read, dom_element_id_write);
1024 	DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "className", dom_element_class_name_read, dom_element_class_name_write);
1025 	DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "attributes", dom_node_attributes_read, NULL);
1026 	DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
1027 	DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
1028 	DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
1029 	DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "previousElementSibling", dom_node_previous_element_sibling_read, NULL);
1030 	DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "nextElementSibling", dom_node_next_element_sibling_read, NULL);
1031 	zend_hash_merge(&dom_modern_element_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
1032 	DOM_OVERWRITE_PROP_HANDLER(&dom_modern_element_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write);
1033 	zend_hash_add_new_ptr(&classes, dom_modern_element_class_entry->name, &dom_modern_element_prop_handlers);
1034 
1035 	dom_text_class_entry = register_class_DOMText(dom_characterdata_class_entry);
1036 	dom_text_class_entry->create_object = dom_objects_new;
1037 	dom_text_class_entry->default_object_handlers = &dom_object_handlers;
1038 
1039 	zend_hash_init(&dom_text_prop_handlers, 0, NULL, NULL, true);
1040 	DOM_REGISTER_PROP_HANDLER(&dom_text_prop_handlers, "wholeText", dom_text_whole_text_read, NULL);
1041 	zend_hash_merge(&dom_text_prop_handlers, &dom_characterdata_prop_handlers, NULL, false);
1042 	zend_hash_add_new_ptr(&classes, dom_text_class_entry->name, &dom_text_prop_handlers);
1043 
1044 	dom_modern_text_class_entry = register_class_DOM_Text(dom_modern_characterdata_class_entry);
1045 	dom_modern_text_class_entry->create_object = dom_objects_new;
1046 	dom_modern_text_class_entry->default_object_handlers = &dom_object_handlers;
1047 
1048 	zend_hash_init(&dom_modern_text_prop_handlers, 0, NULL, NULL, true);
1049 	DOM_REGISTER_PROP_HANDLER(&dom_modern_text_prop_handlers, "wholeText", dom_text_whole_text_read, NULL);
1050 	zend_hash_merge(&dom_modern_text_prop_handlers, &dom_modern_characterdata_prop_handlers, NULL, false);
1051 	zend_hash_add_new_ptr(&classes, dom_modern_text_class_entry->name, &dom_modern_text_prop_handlers);
1052 
1053 	dom_comment_class_entry = register_class_DOMComment(dom_characterdata_class_entry);
1054 	dom_comment_class_entry->create_object = dom_objects_new;
1055 	dom_comment_class_entry->default_object_handlers = &dom_object_handlers;
1056 	zend_hash_add_new_ptr(&classes, dom_comment_class_entry->name, &dom_characterdata_prop_handlers);
1057 
1058 	dom_modern_comment_class_entry = register_class_DOM_Comment(dom_modern_characterdata_class_entry);
1059 	dom_modern_comment_class_entry->create_object = dom_objects_new;
1060 	dom_modern_comment_class_entry->default_object_handlers = &dom_object_handlers;
1061 	zend_hash_add_new_ptr(&classes, dom_modern_comment_class_entry->name, &dom_modern_characterdata_prop_handlers);
1062 
1063 	dom_cdatasection_class_entry = register_class_DOMCdataSection(dom_text_class_entry);
1064 	dom_cdatasection_class_entry->create_object = dom_objects_new;
1065 	dom_cdatasection_class_entry->default_object_handlers = &dom_object_handlers;
1066 	zend_hash_add_new_ptr(&classes, dom_cdatasection_class_entry->name, &dom_text_prop_handlers);
1067 
1068 	dom_modern_cdatasection_class_entry = register_class_DOM_CDATASection(dom_modern_text_class_entry);
1069 	dom_modern_cdatasection_class_entry->create_object = dom_objects_new;
1070 	dom_modern_cdatasection_class_entry->default_object_handlers = &dom_object_handlers;
1071 	zend_hash_add_new_ptr(&classes, dom_modern_cdatasection_class_entry->name, &dom_modern_text_prop_handlers);
1072 
1073 	dom_documenttype_class_entry = register_class_DOMDocumentType(dom_node_class_entry);
1074 	dom_documenttype_class_entry->create_object = dom_objects_new;
1075 	dom_documenttype_class_entry->default_object_handlers = &dom_object_handlers;
1076 
1077 	zend_hash_init(&dom_documenttype_prop_handlers, 0, NULL, NULL, true);
1078 	DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "name", dom_documenttype_name_read, NULL);
1079 	DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "entities", dom_documenttype_entities_read, NULL);
1080 	DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "notations", dom_documenttype_notations_read, NULL);
1081 	DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "publicId", dom_documenttype_public_id_read, NULL);
1082 	DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "systemId", dom_documenttype_system_id_read, NULL);
1083 	DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "internalSubset", dom_documenttype_internal_subset_read, NULL);
1084 	zend_hash_merge(&dom_documenttype_prop_handlers, &dom_node_prop_handlers, NULL, false);
1085 	zend_hash_add_new_ptr(&classes, dom_documenttype_class_entry->name, &dom_documenttype_prop_handlers);
1086 
1087 	dom_modern_documenttype_class_entry = register_class_DOM_DocumentType(dom_modern_node_class_entry, dom_modern_childnode_class_entry);
1088 	dom_modern_documenttype_class_entry->create_object = dom_objects_new;
1089 	dom_modern_documenttype_class_entry->default_object_handlers = &dom_object_handlers;
1090 
1091 	zend_hash_init(&dom_modern_documenttype_prop_handlers, 0, NULL, NULL, true);
1092 	DOM_REGISTER_PROP_HANDLER(&dom_modern_documenttype_prop_handlers, "name", dom_documenttype_name_read, NULL);
1093 	DOM_REGISTER_PROP_HANDLER(&dom_modern_documenttype_prop_handlers, "entities", dom_documenttype_entities_read, NULL);
1094 	DOM_REGISTER_PROP_HANDLER(&dom_modern_documenttype_prop_handlers, "notations", dom_documenttype_notations_read, NULL);
1095 	DOM_REGISTER_PROP_HANDLER(&dom_modern_documenttype_prop_handlers, "publicId", dom_documenttype_public_id_read, NULL);
1096 	DOM_REGISTER_PROP_HANDLER(&dom_modern_documenttype_prop_handlers, "systemId", dom_documenttype_system_id_read, NULL);
1097 	DOM_REGISTER_PROP_HANDLER(&dom_modern_documenttype_prop_handlers, "internalSubset", dom_documenttype_internal_subset_read, NULL);
1098 	zend_hash_merge(&dom_modern_documenttype_prop_handlers, &dom_node_prop_handlers, NULL, false);
1099 	zend_hash_add_new_ptr(&classes, dom_modern_documenttype_class_entry->name, &dom_modern_documenttype_prop_handlers);
1100 
1101 	dom_notation_class_entry = register_class_DOMNotation(dom_node_class_entry);
1102 	dom_notation_class_entry->create_object = dom_objects_new;
1103 	dom_notation_class_entry->default_object_handlers = &dom_object_handlers;
1104 
1105 	zend_hash_init(&dom_notation_prop_handlers, 0, NULL, NULL, true);
1106 	DOM_REGISTER_PROP_HANDLER(&dom_notation_prop_handlers, "publicId", dom_notation_public_id_read, NULL);
1107 	DOM_REGISTER_PROP_HANDLER(&dom_notation_prop_handlers, "systemId", dom_notation_system_id_read, NULL);
1108 	zend_hash_merge(&dom_notation_prop_handlers, &dom_node_prop_handlers, NULL, false);
1109 	zend_hash_add_new_ptr(&classes, dom_notation_class_entry->name, &dom_notation_prop_handlers);
1110 
1111 	dom_modern_notation_class_entry = register_class_DOM_Notation(dom_modern_node_class_entry);
1112 	dom_modern_notation_class_entry->create_object = dom_objects_new;
1113 	dom_modern_notation_class_entry->default_object_handlers = &dom_object_handlers;
1114 
1115 	zend_hash_init(&dom_modern_notation_prop_handlers, 0, NULL, NULL, true);
1116 	DOM_REGISTER_PROP_HANDLER(&dom_modern_notation_prop_handlers, "publicId", dom_notation_public_id_read, NULL);
1117 	DOM_REGISTER_PROP_HANDLER(&dom_modern_notation_prop_handlers, "systemId", dom_notation_system_id_read, NULL);
1118 	zend_hash_merge(&dom_modern_notation_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
1119 	zend_hash_add_new_ptr(&classes, dom_modern_notation_class_entry->name, &dom_modern_node_prop_handlers);
1120 
1121 	dom_entity_class_entry = register_class_DOMEntity(dom_node_class_entry);
1122 	dom_entity_class_entry->create_object = dom_objects_new;
1123 	dom_entity_class_entry->default_object_handlers = &dom_object_handlers;
1124 
1125 	zend_hash_init(&dom_entity_prop_handlers, 0, NULL, NULL, true);
1126 	DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "publicId", dom_entity_public_id_read, NULL);
1127 	DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "systemId", dom_entity_system_id_read, NULL);
1128 	DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "notationName", dom_entity_notation_name_read, NULL);
1129 	DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "actualEncoding", dom_entity_actual_encoding_read, NULL);
1130 	DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "encoding", dom_entity_encoding_read, NULL);
1131 	DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "version", dom_entity_version_read, NULL);
1132 	zend_hash_merge(&dom_entity_prop_handlers, &dom_node_prop_handlers, NULL, false);
1133 	zend_hash_add_new_ptr(&classes, dom_entity_class_entry->name, &dom_entity_prop_handlers);
1134 
1135 	dom_modern_entity_class_entry = register_class_DOM_Entity(dom_modern_node_class_entry);
1136 	dom_modern_entity_class_entry->create_object = dom_objects_new;
1137 	dom_modern_entity_class_entry->default_object_handlers = &dom_object_handlers;
1138 
1139 	zend_hash_init(&dom_modern_entity_prop_handlers, 0, NULL, NULL, true);
1140 	DOM_REGISTER_PROP_HANDLER(&dom_modern_entity_prop_handlers, "publicId", dom_entity_public_id_read, NULL);
1141 	DOM_REGISTER_PROP_HANDLER(&dom_modern_entity_prop_handlers, "systemId", dom_entity_system_id_read, NULL);
1142 	DOM_REGISTER_PROP_HANDLER(&dom_modern_entity_prop_handlers, "notationName", dom_entity_notation_name_read, NULL);
1143 	zend_hash_merge(&dom_modern_entity_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
1144 	zend_hash_add_new_ptr(&classes, dom_modern_entity_class_entry->name, &dom_modern_entity_prop_handlers);
1145 
1146 	dom_entityreference_class_entry = register_class_DOMEntityReference(dom_node_class_entry);
1147 	dom_entityreference_class_entry->create_object = dom_objects_new;
1148 	dom_entityreference_class_entry->default_object_handlers = &dom_object_handlers;
1149 	zend_hash_add_new_ptr(&classes, dom_entityreference_class_entry->name, &dom_node_prop_handlers);
1150 
1151 	dom_modern_entityreference_class_entry = register_class_DOM_EntityReference(dom_modern_node_class_entry);
1152 	dom_modern_entityreference_class_entry->create_object = dom_objects_new;
1153 	dom_modern_entityreference_class_entry->default_object_handlers = &dom_object_handlers;
1154 	zend_hash_add_new_ptr(&classes, dom_modern_entityreference_class_entry->name, &dom_modern_node_prop_handlers);
1155 
1156 	dom_processinginstruction_class_entry = register_class_DOMProcessingInstruction(dom_node_class_entry);
1157 	dom_processinginstruction_class_entry->create_object = dom_objects_new;
1158 	dom_processinginstruction_class_entry->default_object_handlers = &dom_object_handlers;
1159 
1160 	zend_hash_init(&dom_processinginstruction_prop_handlers, 0, NULL, NULL, true);
1161 	DOM_REGISTER_PROP_HANDLER(&dom_processinginstruction_prop_handlers, "target", dom_processinginstruction_target_read, NULL);
1162 	DOM_REGISTER_PROP_HANDLER(&dom_processinginstruction_prop_handlers, "data", dom_processinginstruction_data_read, dom_processinginstruction_data_write);
1163 	zend_hash_merge(&dom_processinginstruction_prop_handlers, &dom_node_prop_handlers, NULL, false);
1164 	zend_hash_add_new_ptr(&classes, dom_processinginstruction_class_entry->name, &dom_processinginstruction_prop_handlers);
1165 
1166 	dom_modern_processinginstruction_class_entry = register_class_DOM_ProcessingInstruction(dom_modern_characterdata_class_entry);
1167 	dom_modern_processinginstruction_class_entry->create_object = dom_objects_new;
1168 	dom_modern_processinginstruction_class_entry->default_object_handlers = &dom_object_handlers;
1169 
1170 	zend_hash_init(&dom_modern_processinginstruction_prop_handlers, 0, NULL, NULL, true);
1171 	DOM_REGISTER_PROP_HANDLER(&dom_modern_processinginstruction_prop_handlers, "target", dom_processinginstruction_target_read, NULL);
1172 	DOM_REGISTER_PROP_HANDLER(&dom_modern_processinginstruction_prop_handlers, "data", dom_processinginstruction_data_read, dom_processinginstruction_data_write);
1173 	zend_hash_merge(&dom_modern_processinginstruction_prop_handlers, &dom_modern_characterdata_prop_handlers, NULL, false);
1174 	zend_hash_add_new_ptr(&classes, dom_modern_processinginstruction_class_entry->name, &dom_modern_processinginstruction_prop_handlers);
1175 
1176 #ifdef LIBXML_XPATH_ENABLED
1177 	memcpy(&dom_xpath_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
1178 	dom_xpath_object_handlers.offset = XtOffsetOf(dom_xpath_object, dom) + XtOffsetOf(dom_object, std);
1179 	dom_xpath_object_handlers.free_obj = dom_xpath_objects_free_storage;
1180 	dom_xpath_object_handlers.get_gc = dom_xpath_get_gc;
1181 	dom_xpath_object_handlers.clone_obj = NULL;
1182 
1183 	dom_xpath_class_entry = register_class_DOMXPath();
1184 	dom_xpath_class_entry->create_object = dom_xpath_objects_new;
1185 	dom_xpath_class_entry->default_object_handlers = &dom_xpath_object_handlers;
1186 
1187 	zend_hash_init(&dom_xpath_prop_handlers, 0, NULL, NULL, true);
1188 	DOM_REGISTER_PROP_HANDLER(&dom_xpath_prop_handlers, "document", dom_xpath_document_read, NULL);
1189 	DOM_REGISTER_PROP_HANDLER(&dom_xpath_prop_handlers, "registerNodeNamespaces", dom_xpath_register_node_ns_read, dom_xpath_register_node_ns_write);
1190 	zend_hash_add_new_ptr(&classes, dom_xpath_class_entry->name, &dom_xpath_prop_handlers);
1191 
1192 	dom_modern_xpath_class_entry = register_class_DOM_XPath();
1193 	dom_modern_xpath_class_entry->create_object = dom_xpath_objects_new;
1194 	dom_modern_xpath_class_entry->default_object_handlers = &dom_xpath_object_handlers;
1195 
1196 	zend_hash_add_new_ptr(&classes, dom_modern_xpath_class_entry->name, &dom_xpath_prop_handlers);
1197 #endif
1198 
1199 	register_php_dom_symbols(module_number);
1200 
1201 	php_libxml_register_export(dom_node_class_entry, php_dom_export_node);
1202 	php_libxml_register_export(dom_modern_node_class_entry, php_dom_export_node);
1203 
1204 	lexbor_memory_setup(dom_malloc, dom_realloc, dom_calloc, dom_free);
1205 
1206 	return SUCCESS;
1207 }
1208 /* }}} */
1209 
1210 /* {{{ */
PHP_MINFO_FUNCTION(dom)1211 PHP_MINFO_FUNCTION(dom)
1212 {
1213 	php_info_print_table_start();
1214 	php_info_print_table_row(2, "DOM/XML", "enabled");
1215 	php_info_print_table_row(2, "DOM/XML API Version", DOM_API_VERSION);
1216 	php_info_print_table_row(2, "libxml Version", LIBXML_DOTTED_VERSION);
1217 #ifdef LIBXML_HTML_ENABLED
1218 	php_info_print_table_row(2, "HTML Support", "enabled");
1219 #endif
1220 #ifdef LIBXML_XPATH_ENABLED
1221 	php_info_print_table_row(2, "XPath Support", "enabled");
1222 #endif
1223 #ifdef LIBXML_XPTR_ENABLED
1224 	php_info_print_table_row(2, "XPointer Support", "enabled");
1225 #endif
1226 #ifdef LIBXML_SCHEMAS_ENABLED
1227 	php_info_print_table_row(2, "Schema Support", "enabled");
1228 	php_info_print_table_row(2, "RelaxNG Support", "enabled");
1229 #endif
1230 	php_info_print_table_end();
1231 }
1232 /* }}} */
1233 
PHP_MSHUTDOWN_FUNCTION(dom)1234 PHP_MSHUTDOWN_FUNCTION(dom) /* {{{ */
1235 {
1236 	zend_hash_destroy(&dom_document_prop_handlers);
1237 	zend_hash_destroy(&dom_abstract_base_document_prop_handlers);
1238 	zend_hash_destroy(&dom_xml_document_prop_handlers);
1239 	zend_hash_destroy(&dom_documentfragment_prop_handlers);
1240 	zend_hash_destroy(&dom_modern_documentfragment_prop_handlers);
1241 	zend_hash_destroy(&dom_node_prop_handlers);
1242 	zend_hash_destroy(&dom_modern_node_prop_handlers);
1243 	zend_hash_destroy(&dom_namespace_node_prop_handlers);
1244 	zend_hash_destroy(&dom_nodelist_prop_handlers);
1245 	zend_hash_destroy(&dom_namednodemap_prop_handlers);
1246 	zend_hash_destroy(&dom_characterdata_prop_handlers);
1247 	zend_hash_destroy(&dom_modern_characterdata_prop_handlers);
1248 	zend_hash_destroy(&dom_attr_prop_handlers);
1249 	zend_hash_destroy(&dom_modern_attr_prop_handlers);
1250 	zend_hash_destroy(&dom_element_prop_handlers);
1251 	zend_hash_destroy(&dom_modern_element_prop_handlers);
1252 	zend_hash_destroy(&dom_text_prop_handlers);
1253 	zend_hash_destroy(&dom_modern_text_prop_handlers);
1254 	zend_hash_destroy(&dom_documenttype_prop_handlers);
1255 	zend_hash_destroy(&dom_modern_documenttype_prop_handlers);
1256 	zend_hash_destroy(&dom_notation_prop_handlers);
1257 	zend_hash_destroy(&dom_modern_notation_prop_handlers);
1258 	zend_hash_destroy(&dom_entity_prop_handlers);
1259 	zend_hash_destroy(&dom_modern_entity_prop_handlers);
1260 	zend_hash_destroy(&dom_processinginstruction_prop_handlers);
1261 	zend_hash_destroy(&dom_modern_processinginstruction_prop_handlers);
1262 #ifdef LIBXML_XPATH_ENABLED
1263 	zend_hash_destroy(&dom_xpath_prop_handlers);
1264 #endif
1265 	zend_hash_destroy(&classes);
1266 
1267 /*	If you want do find memleaks in this module, compile libxml2 with --with-mem-debug and
1268 	uncomment the following line, this will tell you the amount of not freed memory
1269 	and the total used memory into apaches error_log  */
1270 /*  xmlMemoryDump();*/
1271 
1272 	return SUCCESS;
1273 }
1274 /* }}} */
1275 
1276 /* {{{ node_list_unlink */
node_list_unlink(xmlNodePtr node)1277 void node_list_unlink(xmlNodePtr node)
1278 {
1279 	dom_object *wrapper;
1280 
1281 	while (node != NULL) {
1282 
1283 		wrapper = php_dom_object_get_data(node);
1284 
1285 		if (wrapper != NULL ) {
1286 			xmlUnlinkNode(node);
1287 		} else {
1288 			if (node->type == XML_ENTITY_REF_NODE)
1289 				break;
1290 			node_list_unlink(node->children);
1291 
1292 			switch (node->type) {
1293 				case XML_ATTRIBUTE_DECL:
1294 				case XML_DTD_NODE:
1295 				case XML_DOCUMENT_TYPE_NODE:
1296 				case XML_ENTITY_DECL:
1297 				case XML_ATTRIBUTE_NODE:
1298 				case XML_TEXT_NODE:
1299 					break;
1300 				default:
1301 					node_list_unlink((xmlNodePtr) node->properties);
1302 			}
1303 
1304 		}
1305 
1306 		node = node->next;
1307 	}
1308 }
1309 /* }}} end node_list_unlink */
1310 
1311 /* {{{ dom_objects_free_storage */
dom_objects_free_storage(zend_object * object)1312 void dom_objects_free_storage(zend_object *object)
1313 {
1314 	dom_object *intern = php_dom_obj_from_obj(object);
1315 
1316 	zend_object_std_dtor(&intern->std);
1317 
1318 	if (intern->ptr != NULL && ((php_libxml_node_ptr *)intern->ptr)->node != NULL) {
1319 		if (((xmlNodePtr) ((php_libxml_node_ptr *)intern->ptr)->node)->type != XML_DOCUMENT_NODE && ((xmlNodePtr) ((php_libxml_node_ptr *)intern->ptr)->node)->type != XML_HTML_DOCUMENT_NODE) {
1320 			php_libxml_node_decrement_resource((php_libxml_node_object *) intern);
1321 		} else {
1322 			php_libxml_decrement_node_ptr((php_libxml_node_object *) intern);
1323 			php_libxml_decrement_doc_ref((php_libxml_node_object *) intern);
1324 		}
1325 		intern->ptr = NULL;
1326 	}
1327 }
1328 /* }}} */
1329 
dom_namednode_iter(dom_object * basenode,int ntype,dom_object * intern,xmlHashTablePtr ht,const char * local,size_t local_len,const char * ns,size_t ns_len)1330 void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xmlHashTablePtr ht, const char *local, size_t local_len, const char *ns, size_t ns_len) /* {{{ */
1331 {
1332 	dom_nnodemap_object *mapptr = (dom_nnodemap_object *) intern->ptr;
1333 
1334 	ZEND_ASSERT(basenode != NULL);
1335 
1336 	ZVAL_OBJ_COPY(&mapptr->baseobj_zv, &basenode->std);
1337 
1338 	xmlDocPtr doc = basenode->document ? basenode->document->ptr : NULL;
1339 
1340 	mapptr->baseobj = basenode;
1341 	mapptr->nodetype = ntype;
1342 	mapptr->ht = ht;
1343 
1344 	const xmlChar* tmp;
1345 
1346 	if (local) {
1347 		int len = local_len > INT_MAX ? -1 : (int) local_len;
1348 		if (doc != NULL && (tmp = xmlDictExists(doc->dict, (const xmlChar *)local, len)) != NULL) {
1349 			mapptr->local = BAD_CAST tmp;
1350 		} else {
1351 			mapptr->local = xmlCharStrndup(local, len);
1352 			mapptr->free_local = true;
1353 		}
1354 		mapptr->local_lower = BAD_CAST estrdup(local);
1355 		if (len < 0) {
1356 			zend_str_tolower((char *) mapptr->local_lower, strlen((const char *) mapptr->local_lower));
1357 		} else {
1358 			zend_str_tolower((char *) mapptr->local_lower, len);
1359 		}
1360 	}
1361 
1362 	if (ns) {
1363 		int len = ns_len > INT_MAX ? -1 : (int) ns_len;
1364 		if (doc != NULL && (tmp = xmlDictExists(doc->dict, (const xmlChar *)ns, len)) != NULL) {
1365 			mapptr->ns = BAD_CAST tmp;
1366 		} else {
1367 			mapptr->ns = xmlCharStrndup(ns, len);
1368 			mapptr->free_ns = true;
1369 		}
1370 	}
1371 }
1372 /* }}} */
1373 
dom_objects_set_class_ex(zend_class_entry * class_type,dom_object * intern)1374 static void dom_objects_set_class_ex(zend_class_entry *class_type, dom_object *intern)
1375 {
1376 	zend_class_entry *base_class = class_type;
1377 	while ((base_class->type != ZEND_INTERNAL_CLASS || base_class->info.internal.module->module_number != dom_module_entry.module_number) && base_class->parent != NULL) {
1378 		base_class = base_class->parent;
1379 	}
1380 
1381 	intern->prop_handler = zend_hash_find_ptr(&classes, base_class->name);
1382 
1383 	zend_object_std_init(&intern->std, class_type);
1384 	object_properties_init(&intern->std, class_type);
1385 }
1386 
dom_objects_set_class(zend_class_entry * class_type)1387 static dom_object* dom_objects_set_class(zend_class_entry *class_type)
1388 {
1389 	dom_object *intern = zend_object_alloc(sizeof(dom_object), class_type);
1390 	dom_objects_set_class_ex(class_type, intern);
1391 	return intern;
1392 }
1393 
1394 /* {{{ dom_objects_new */
dom_objects_new(zend_class_entry * class_type)1395 zend_object *dom_objects_new(zend_class_entry *class_type)
1396 {
1397 	dom_object *intern = dom_objects_set_class(class_type);
1398 	return &intern->std;
1399 }
1400 /* }}} */
1401 
dom_objects_namespace_node_new(zend_class_entry * class_type)1402 static zend_object *dom_objects_namespace_node_new(zend_class_entry *class_type)
1403 {
1404 	dom_object_namespace_node *intern = zend_object_alloc(sizeof(dom_object_namespace_node), class_type);
1405 	dom_objects_set_class_ex(class_type, &intern->dom);
1406 	return &intern->dom.std;
1407 }
1408 
dom_object_namespace_node_free_storage(zend_object * object)1409 static void dom_object_namespace_node_free_storage(zend_object *object)
1410 {
1411 	dom_object_namespace_node *intern = php_dom_namespace_node_obj_from_obj(object);
1412 	if (intern->parent_intern != NULL) {
1413 		zval tmp;
1414 		ZVAL_OBJ(&tmp, &intern->parent_intern->std);
1415 		zval_ptr_dtor(&tmp);
1416 	}
1417 	dom_objects_free_storage(object);
1418 }
1419 
1420 #ifdef LIBXML_XPATH_ENABLED
1421 
1422 /* {{{ zend_object dom_xpath_objects_new(zend_class_entry *class_type) */
dom_xpath_objects_new(zend_class_entry * class_type)1423 zend_object *dom_xpath_objects_new(zend_class_entry *class_type)
1424 {
1425 	dom_xpath_object *intern = zend_object_alloc(sizeof(dom_xpath_object), class_type);
1426 
1427 	php_dom_xpath_callbacks_ctor(&intern->xpath_callbacks);
1428 	intern->register_node_ns = 1;
1429 
1430 	intern->dom.prop_handler = &dom_xpath_prop_handlers;
1431 
1432 	zend_object_std_init(&intern->dom.std, class_type);
1433 	object_properties_init(&intern->dom.std, class_type);
1434 
1435 	return &intern->dom.std;
1436 }
1437 /* }}} */
1438 
1439 #endif
1440 
dom_nnodemap_objects_free_storage(zend_object * object)1441 void dom_nnodemap_objects_free_storage(zend_object *object) /* {{{ */
1442 {
1443 	dom_object *intern = php_dom_obj_from_obj(object);
1444 	dom_nnodemap_object *objmap = (dom_nnodemap_object *)intern->ptr;
1445 
1446 	if (objmap) {
1447 		if (objmap->cached_obj && GC_DELREF(&objmap->cached_obj->std) == 0) {
1448 			zend_objects_store_del(&objmap->cached_obj->std);
1449 		}
1450 		if (objmap->free_local) {
1451 			xmlFree(objmap->local);
1452 		}
1453 		if (objmap->free_ns) {
1454 			xmlFree(objmap->ns);
1455 		}
1456 		if (objmap->local_lower) {
1457 			efree(objmap->local_lower);
1458 		}
1459 		if (!Z_ISUNDEF(objmap->baseobj_zv)) {
1460 			zval_ptr_dtor(&objmap->baseobj_zv);
1461 		}
1462 		efree(objmap);
1463 		intern->ptr = NULL;
1464 	}
1465 
1466 	php_libxml_decrement_doc_ref((php_libxml_node_object *)intern);
1467 
1468 	zend_object_std_dtor(&intern->std);
1469 }
1470 /* }}} */
1471 
dom_nnodemap_objects_new(zend_class_entry * class_type)1472 zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type)
1473 {
1474 	dom_object *intern;
1475 	dom_nnodemap_object *objmap;
1476 
1477 	intern = dom_objects_set_class(class_type);
1478 	intern->ptr = emalloc(sizeof(dom_nnodemap_object));
1479 	objmap = (dom_nnodemap_object *)intern->ptr;
1480 	ZVAL_UNDEF(&objmap->baseobj_zv);
1481 	objmap->baseobj = NULL;
1482 	objmap->nodetype = 0;
1483 	objmap->ht = NULL;
1484 	objmap->local = NULL;
1485 	objmap->local_lower = NULL;
1486 	objmap->free_local = false;
1487 	objmap->ns = NULL;
1488 	objmap->free_ns = false;
1489 	objmap->cache_tag.modification_nr = 0;
1490 	objmap->cached_length = -1;
1491 	objmap->cached_obj = NULL;
1492 	objmap->cached_obj_index = 0;
1493 
1494 	return &intern->std;
1495 }
1496 
php_dom_create_iterator(zval * return_value,dom_iterator_type iterator_type,bool modern)1497 void php_dom_create_iterator(zval *return_value, dom_iterator_type iterator_type, bool modern) /* {{{ */
1498 {
1499 	zend_class_entry *ce;
1500 
1501 	if (iterator_type == DOM_NAMEDNODEMAP) {
1502 		ce = dom_get_namednodemap_ce(modern);
1503 	} else if (iterator_type == DOM_HTMLCOLLECTION) {
1504 		/* This only exists in modern DOM. */
1505 		ZEND_ASSERT(modern);
1506 		ce = dom_html_collection_class_entry;
1507 	} else if (iterator_type == DOM_DTD_NAMEDNODEMAP) {
1508 		ce = dom_get_dtd_namednodemap_ce(modern);
1509 	} else {
1510 		ZEND_ASSERT(iterator_type == DOM_NODELIST);
1511 		ce = dom_get_nodelist_ce(modern);
1512 	}
1513 
1514 	object_init_ex(return_value, ce);
1515 }
1516 /* }}} */
1517 
1518 /* {{{ php_dom_create_object */
php_dom_create_object(xmlNodePtr obj,zval * return_value,dom_object * domobj)1519 PHP_DOM_EXPORT bool php_dom_create_object(xmlNodePtr obj, zval *return_value, dom_object *domobj)
1520 {
1521 	dom_object *intern = php_dom_object_get_data(obj);
1522 	if (intern) {
1523 		ZVAL_OBJ_COPY(return_value, &intern->std);
1524 		return true;
1525 	}
1526 
1527 	bool modern = domobj && php_dom_follow_spec_intern(domobj);
1528 
1529 	zend_class_entry *ce;
1530 	switch (obj->type) {
1531 		case XML_DOCUMENT_NODE:
1532 		{
1533 			ce = dom_get_xml_document_ce(modern);
1534 			break;
1535 		}
1536 		case XML_HTML_DOCUMENT_NODE:
1537 		{
1538 			ce = dom_get_html_document_ce(modern);
1539 			break;
1540 		}
1541 		case XML_DTD_NODE:
1542 		case XML_DOCUMENT_TYPE_NODE:
1543 		{
1544 			ce = dom_get_documenttype_ce(modern);
1545 			break;
1546 		}
1547 		case XML_ELEMENT_NODE:
1548 		{
1549 			ce = dom_get_element_ce(modern);
1550 			break;
1551 		}
1552 		case XML_ATTRIBUTE_NODE:
1553 		{
1554 			ce = dom_get_attr_ce(modern);
1555 			break;
1556 		}
1557 		case XML_TEXT_NODE:
1558 		{
1559 			ce = dom_get_text_ce(modern);
1560 			break;
1561 		}
1562 		case XML_COMMENT_NODE:
1563 		{
1564 			ce = dom_get_comment_ce(modern);
1565 			break;
1566 		}
1567 		case XML_PI_NODE:
1568 		{
1569 			ce = dom_get_processinginstruction_ce(modern);
1570 			break;
1571 		}
1572 		case XML_ENTITY_REF_NODE:
1573 		{
1574 			ce = dom_get_entityreference_ce(modern);
1575 			break;
1576 		}
1577 		case XML_ENTITY_DECL:
1578 		case XML_ELEMENT_DECL:
1579 		{
1580 			ce = dom_get_entity_ce(modern);
1581 			break;
1582 		}
1583 		case XML_CDATA_SECTION_NODE:
1584 		{
1585 			ce = dom_get_cdatasection_ce(modern);
1586 			break;
1587 		}
1588 		case XML_DOCUMENT_FRAG_NODE:
1589 		{
1590 			ce = dom_get_documentfragment_ce(modern);
1591 			break;
1592 		}
1593 		case XML_NOTATION_NODE:
1594 		{
1595 			ce = dom_get_notation_ce(modern);
1596 			break;
1597 		}
1598 		case XML_NAMESPACE_DECL:
1599 		{
1600 			/* This has no modern equivalent */
1601 			ce = dom_namespace_node_class_entry;
1602 			break;
1603 		}
1604 		default:
1605 			/* TODO you can actually hit this with fixed attributes in the DTD for example... */
1606 			zend_throw_error(NULL, "Unsupported node type: %d", obj->type);
1607 			ZVAL_NULL(return_value);
1608 			return false;
1609 	}
1610 
1611 	if (domobj && domobj->document) {
1612 		ce = dom_get_doc_classmap(domobj->document, ce);
1613 	}
1614 	php_dom_instantiate_object_helper(return_value, ce, obj, domobj);
1615 	return false;
1616 }
1617 /* }}} end php_domobject_new */
1618 
php_dom_instantiate_object_helper(zval * return_value,zend_class_entry * ce,xmlNodePtr obj,dom_object * parent)1619 dom_object *php_dom_instantiate_object_helper(zval *return_value, zend_class_entry *ce, xmlNodePtr obj, dom_object *parent)
1620 {
1621 	object_init_ex(return_value, ce);
1622 
1623 	dom_object *intern = Z_DOMOBJ_P(return_value);
1624 	if (obj->doc != NULL) {
1625 		if (parent != NULL) {
1626 			intern->document = parent->document;
1627 		}
1628 		php_libxml_increment_doc_ref((php_libxml_node_object *)intern, obj->doc);
1629 	}
1630 
1631 	php_libxml_increment_node_ptr((php_libxml_node_object *)intern, obj, (void *)intern);
1632 
1633 	return intern;
1634 }
1635 
php_dom_create_implementation(zval * retval,bool modern)1636 void php_dom_create_implementation(zval *retval, bool modern) {
1637 	object_init_ex(retval, dom_get_domimplementation_ce(modern));
1638 }
1639 
1640 /* {{{ int dom_hierarchy(xmlNodePtr parent, xmlNodePtr child) */
dom_hierarchy(xmlNodePtr parent,xmlNodePtr child)1641 int dom_hierarchy(xmlNodePtr parent, xmlNodePtr child)
1642 {
1643 	xmlNodePtr nodep;
1644 
1645 	if (parent == NULL || child == NULL || child->doc != parent->doc) {
1646 		return SUCCESS;
1647 	}
1648 
1649 	if (child->type == XML_DOCUMENT_NODE) {
1650 		return FAILURE;
1651 	}
1652 
1653 	nodep = parent;
1654 
1655 	while (nodep) {
1656 		if (nodep == child) {
1657 			return FAILURE;
1658 		}
1659 		nodep = nodep->parent;
1660 	}
1661 
1662 	return SUCCESS;
1663 }
1664 /* }}} end dom_hierarchy */
1665 
1666 /* {{{ */
dom_has_feature(zend_string * feature,zend_string * version)1667 bool dom_has_feature(zend_string *feature, zend_string *version)
1668 {
1669 	if (zend_string_equals_literal(version, "1.0")
1670 		|| zend_string_equals_literal(version, "2.0")
1671 		|| zend_string_equals_literal(version, "")
1672 	) {
1673 		if (zend_string_equals_literal_ci(feature, "XML")
1674 			|| (zend_string_equals_literal_ci(feature, "Core") && zend_string_equals_literal(version, "1.0"))
1675 		) {
1676 			return true;
1677 		}
1678 	}
1679 
1680 	return false;
1681 }
1682 /* }}} end dom_has_feature */
1683 
dom_match_qualified_name_according_to_spec(const xmlChar * qname,const xmlNode * nodep)1684 bool dom_match_qualified_name_according_to_spec(const xmlChar *qname, const xmlNode *nodep)
1685 {
1686 	const xmlChar *node_local = nodep->name;
1687 
1688 	/* The qualified name must be matched, which means either:
1689 	 *  - The local parts are equal and there is no ns prefix for this element (i.e. the fqn is in the local name).
1690 	 *  - There is a prefix, the prefixes are equal and the local parts are equal. */
1691 	if (nodep->ns != NULL && nodep->ns->prefix != NULL) {
1692 		const char *prefix = (const char *) nodep->ns->prefix;
1693 		/* 1. match prefix up to |prefix| characters.
1694 		 *    This won't overflow as it'll stop at the '\0' if the lengths don't match. */
1695 		size_t prefix_len = strlen(prefix);
1696 		if (strncmp((const char *) qname, prefix, prefix_len) != 0) {
1697 			return false;
1698 		}
1699 		/* 2. match ':' */
1700 		if (qname[prefix_len] != ':') {
1701 			return false;
1702 		}
1703 		/* 3. match local name */
1704 		return xmlStrEqual(qname + prefix_len + 1, node_local);
1705 	} else {
1706 		return xmlStrEqual(node_local, qname);
1707 	}
1708 }
1709 
dom_match_qualified_name_for_tag_name_equality(const xmlChar * local,const xmlChar * local_lower,const xmlNode * nodep,bool match_qname)1710 static bool dom_match_qualified_name_for_tag_name_equality(const xmlChar *local, const xmlChar *local_lower, const xmlNode *nodep, bool match_qname)
1711 {
1712 	if (!match_qname) {
1713 		return xmlStrEqual(nodep->name, local);
1714 	}
1715 
1716 	const xmlChar *local_to_use = nodep->doc->type == XML_HTML_DOCUMENT_NODE && php_dom_ns_is_fast(nodep, php_dom_ns_is_html_magic_token) ? local_lower : local;
1717 	return dom_match_qualified_name_according_to_spec(local_to_use, nodep);
1718 }
1719 
dom_get_elements_by_tag_name_ns_raw(xmlNodePtr basep,xmlNodePtr nodep,xmlChar * ns,xmlChar * local,xmlChar * local_lower,int * cur,int index)1720 xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr basep, xmlNodePtr nodep, xmlChar *ns, xmlChar *local, xmlChar *local_lower, int *cur, int index) /* {{{ */
1721 {
1722 	/* Can happen with detached document */
1723 	if (UNEXPECTED(nodep == NULL)) {
1724 		return NULL;
1725 	}
1726 
1727 	xmlNodePtr ret = NULL;
1728 	bool local_match_any = local[0] == '*' && local[1] == '\0';
1729 
1730 	/* Note: The spec says that ns == '' must be transformed to ns == NULL. In other words, they are equivalent.
1731 	 *       PHP however does not do this and internally uses the empty string everywhere when the user provides ns == NULL.
1732 	 *       This is because for PHP ns == NULL has another meaning: "match every namespace" instead of "match the empty namespace". */
1733 	bool ns_match_any = ns == NULL || (ns[0] == '*' && ns[1] == '\0');
1734 
1735 	bool match_qname = ns == NULL && php_dom_follow_spec_node(basep);
1736 
1737 	while (*cur <= index) {
1738 		if (nodep->type == XML_ELEMENT_NODE) {
1739 			if (local_match_any || dom_match_qualified_name_for_tag_name_equality(local, local_lower, nodep, match_qname)) {
1740 				if (ns_match_any || (ns[0] == '\0' && nodep->ns == NULL) || (nodep->ns != NULL && xmlStrEqual(nodep->ns->href, ns))) {
1741 					if (*cur == index) {
1742 						ret = nodep;
1743 						break;
1744 					}
1745 					(*cur)++;
1746 				}
1747 			}
1748 
1749 			if (nodep->children) {
1750 				nodep = nodep->children;
1751 				continue;
1752 			}
1753 		}
1754 
1755 		nodep = php_dom_next_in_tree_order(nodep, basep);
1756 		if (!nodep) {
1757 			return NULL;
1758 		}
1759 	}
1760 	return ret;
1761 }
1762 /* }}} end dom_element_get_elements_by_tag_name_ns_raw */
1763 
is_empty_node(xmlNodePtr nodep)1764 static inline bool is_empty_node(xmlNodePtr nodep)
1765 {
1766 	return nodep->content == NULL || *nodep->content == '\0';
1767 }
1768 
free_node(xmlNodePtr node)1769 static inline void free_node(xmlNodePtr node)
1770 {
1771 	if (node->_private == NULL) {
1772 		xmlFreeNode(node);
1773 	}
1774 }
1775 
dom_merge_adjacent_exclusive_text_nodes(xmlNodePtr node)1776 static void dom_merge_adjacent_exclusive_text_nodes(xmlNodePtr node)
1777 {
1778 	xmlNodePtr nextp = node->next;
1779 	while (nextp != NULL && nextp->type == XML_TEXT_NODE) {
1780 		xmlNodePtr newnextp = nextp->next;
1781 		xmlChar *strContent = nextp->content;
1782 		if (strContent != NULL) {
1783 			xmlNodeAddContent(node, strContent);
1784 		}
1785 		xmlUnlinkNode(nextp);
1786 		free_node(nextp);
1787 		nextp = newnextp;
1788 	}
1789 }
1790 
1791 /* {{{ void php_dom_normalize_legacy(xmlNodePtr nodep) */
php_dom_normalize_legacy(xmlNodePtr nodep)1792 void php_dom_normalize_legacy(xmlNodePtr nodep)
1793 {
1794 	xmlNodePtr child = nodep->children;
1795 	while(child != NULL) {
1796 		switch (child->type) {
1797 			case XML_TEXT_NODE:
1798 				dom_merge_adjacent_exclusive_text_nodes(child);
1799 				if (is_empty_node(child)) {
1800 					xmlNodePtr nextp = child->next;
1801 					xmlUnlinkNode(child);
1802 					free_node(child);
1803 					child = nextp;
1804 					continue;
1805 				}
1806 				break;
1807 			case XML_ELEMENT_NODE:
1808 				php_dom_normalize_legacy(child);
1809 				xmlAttrPtr attr = child->properties;
1810 				while (attr != NULL) {
1811 					php_dom_normalize_legacy((xmlNodePtr) attr);
1812 					attr = attr->next;
1813 				}
1814 				break;
1815 			default:
1816 				break;
1817 		}
1818 		child = child->next;
1819 	}
1820 }
1821 /* }}} end php_dom_normalize_legacy */
1822 
1823 /* https://dom.spec.whatwg.org/#dom-node-normalize */
php_dom_normalize_modern(xmlNodePtr this)1824 void php_dom_normalize_modern(xmlNodePtr this)
1825 {
1826 	/* for each descendant exclusive Text node node of this: */
1827 	xmlNodePtr node = this->children;
1828 	while (node != NULL) {
1829 		if (node->type == XML_TEXT_NODE) {
1830 			/* 1. Let length be node’s length.
1831 			 *    We'll deviate a bit here: we'll just check if it's empty or not as we don't want to compute the length. */
1832 			bool is_empty = is_empty_node(node);
1833 
1834 			/* 2. If length is zero, then remove node and continue with the next exclusive Text node, if any. */
1835 			if (is_empty) {
1836 				xmlNodePtr next = node->next;
1837 				xmlUnlinkNode(node);
1838 				free_node(node);
1839 				node = next;
1840 				continue;
1841 			}
1842 
1843 			/* 3. Let data be the concatenation of the data of node’s contiguous exclusive Text nodes (excluding itself), in tree order.
1844 			 * 4. Replace data with node node, offset length, count 0, and data data.
1845 			 * 7. Remove node’s contiguous exclusive Text nodes (excluding itself), in tree order.
1846 			 *    => In other words: Concat every contiguous text node into node and delete the merged nodes. */
1847 			dom_merge_adjacent_exclusive_text_nodes(node);
1848 
1849 			/* Steps 5-6 deal with mutation records, we don't do that here. */
1850 		} else if (node->type == XML_ELEMENT_NODE) {
1851 			php_dom_normalize_modern(node);
1852 		}
1853 		node = node->next;
1854 	}
1855 }
1856 
dom_reconcile_ns_internal(xmlDocPtr doc,xmlNodePtr nodep,xmlNodePtr search_parent)1857 static void dom_reconcile_ns_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr search_parent)
1858 {
1859 	xmlNsPtr nsptr, nsdftptr, curns, prevns = NULL;
1860 
1861 	/* Following if block primarily used for inserting nodes created via createElementNS */
1862 	if (nodep->nsDef != NULL) {
1863 		curns = nodep->nsDef;
1864 		while (curns) {
1865 			nsdftptr = curns->next;
1866 			if (curns->href != NULL) {
1867 				if((nsptr = xmlSearchNsByHref(doc, search_parent, curns->href)) &&
1868 					(curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) {
1869 					curns->next = NULL;
1870 					if (prevns == NULL) {
1871 						nodep->nsDef = nsdftptr;
1872 					} else {
1873 						prevns->next = nsdftptr;
1874 					}
1875 					/* Note: we can't get here if the ns is already on the oldNs list.
1876 					 * This is because in that case the definition won't be on the node, and
1877 					 * therefore won't be in the nodep->nsDef list. */
1878 					php_libxml_set_old_ns(doc, curns);
1879 					curns = prevns;
1880 				}
1881 			}
1882 			prevns = curns;
1883 			curns = nsdftptr;
1884 		}
1885 	}
1886 }
1887 
dom_reconcile_ns(xmlDocPtr doc,xmlNodePtr nodep)1888 void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */
1889 {
1890 	ZEND_ASSERT(nodep->type != XML_ATTRIBUTE_NODE);
1891 
1892 	/* Although the node type will be checked by the libxml2 API,
1893 	 * we still want to do the internal reconciliation conditionally. */
1894 	if (nodep->type == XML_ELEMENT_NODE) {
1895 		dom_reconcile_ns_internal(doc, nodep, nodep->parent);
1896 		xmlReconciliateNs(doc, nodep);
1897 	}
1898 }
1899 /* }}} */
1900 
dom_reconcile_ns_list_internal(xmlDocPtr doc,xmlNodePtr nodep,xmlNodePtr last,xmlNodePtr search_parent)1901 static void dom_reconcile_ns_list_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last, xmlNodePtr search_parent)
1902 {
1903 	ZEND_ASSERT(nodep != NULL);
1904 	while (true) {
1905 		if (nodep->type == XML_ELEMENT_NODE) {
1906 			dom_reconcile_ns_internal(doc, nodep, search_parent);
1907 			if (nodep->children) {
1908 				dom_reconcile_ns_list_internal(doc, nodep->children, nodep->last /* process the whole children list */, search_parent);
1909 			}
1910 		}
1911 		if (nodep == last) {
1912 			break;
1913 		}
1914 		nodep = nodep->next;
1915 	}
1916 }
1917 
dom_reconcile_ns_list(xmlDocPtr doc,xmlNodePtr nodep,xmlNodePtr last)1918 void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last)
1919 {
1920 	dom_reconcile_ns_list_internal(doc, nodep, last, nodep->parent);
1921 	/* The loop is outside of the recursion in the above call because
1922 	 * dom_libxml_reconcile_ensure_namespaces_are_declared() performs its own recursion. */
1923 	while (true) {
1924 		/* The internal libxml2 call will already check the node type, no need for us to do it here. */
1925 		xmlReconciliateNs(doc, nodep);
1926 		if (nodep == last) {
1927 			break;
1928 		}
1929 		nodep = nodep->next;
1930 	}
1931 }
1932 
1933 /* https://dom.spec.whatwg.org/#validate-and-extract */
dom_validate_and_extract(const zend_string * namespace,const zend_string * qname,xmlChar ** localName,xmlChar ** prefix)1934 int dom_validate_and_extract(const zend_string *namespace, const zend_string *qname, xmlChar **localName, xmlChar **prefix)
1935 {
1936 	/* 1. If namespace is the empty string, then set it to null.
1937 	 *    However, we're going to cheat and do the opposite to make
1938 	 *    implementation of the below steps with existing zend_ helpers easier. */
1939 	if (namespace == NULL) {
1940 		namespace = zend_empty_string;
1941 	}
1942 
1943 	/* 2. Validate qualifiedName. */
1944 	if (xmlValidateQName(BAD_CAST ZSTR_VAL(qname), /* allow spaces */ 0) != 0) {
1945 		return INVALID_CHARACTER_ERR;
1946 	}
1947 
1948 	/* Steps 3-5 */
1949 	*localName = xmlSplitQName2(BAD_CAST ZSTR_VAL(qname), prefix);
1950 
1951 	/* 6. If prefix is non-null and namespace is null, then throw a "NamespaceError" DOMException.
1952 	 *    Note that null namespace means empty string here becaue of step 1. */
1953 	if (*prefix != NULL && ZSTR_VAL(namespace)[0] == '\0') {
1954 		return NAMESPACE_ERR;
1955 	}
1956 
1957 	/* 7. If prefix is "xml" and namespace is not the XML namespace, then throw a "NamespaceError" DOMException. */
1958 	if (UNEXPECTED(!zend_string_equals_literal(namespace, "http://www.w3.org/XML/1998/namespace") && xmlStrEqual(*prefix, BAD_CAST "xml"))) {
1959 		return NAMESPACE_ERR;
1960 	}
1961 
1962 	/* 8. If either qualifiedName or prefix is "xmlns" and namespace is not the XMLNS namespace, then throw a "NamespaceError" DOMException. */
1963 	if (UNEXPECTED((zend_string_equals_literal(qname, "xmlns") || xmlStrEqual(*prefix, BAD_CAST "xmlns")) && !zend_string_equals_literal(namespace, "http://www.w3.org/2000/xmlns/"))) {
1964 		return NAMESPACE_ERR;
1965 	}
1966 
1967 	/* 9. If namespace is the XMLNS namespace and neither qualifiedName nor prefix is "xmlns", then throw a "NamespaceError" DOMException. */
1968 	if (UNEXPECTED(zend_string_equals_literal(namespace, "http://www.w3.org/2000/xmlns/") && !zend_string_equals_literal(qname, "xmlns") && !xmlStrEqual(*prefix, BAD_CAST "xmlns"))) {
1969 		return NAMESPACE_ERR;
1970 	}
1971 
1972 	if (*localName == NULL) {
1973 		*localName = xmlStrdup(BAD_CAST ZSTR_VAL(qname));
1974 	}
1975 
1976 	return 0;
1977 }
1978 
1979 /*
1980 http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS
1981 
1982 NAMESPACE_ERR: Raised if
1983 
1984 1. the qualifiedName is a malformed qualified name
1985 2. the qualifiedName has a prefix and the  namespaceURI is null
1986 */
1987 
1988 /* {{{ int dom_check_qname(char *qname, char **localname, char **prefix, int uri_len, int name_len) */
dom_check_qname(char * qname,char ** localname,char ** prefix,int uri_len,int name_len)1989 int dom_check_qname(char *qname, char **localname, char **prefix, int uri_len, int name_len) {
1990 	if (name_len == 0) {
1991 		return NAMESPACE_ERR;
1992 	}
1993 
1994 	*localname = (char *)xmlSplitQName2(BAD_CAST qname, (xmlChar **) prefix);
1995 	if (*localname == NULL) {
1996 		*localname = (char *)xmlStrdup(BAD_CAST qname);
1997 		if (*prefix == NULL && uri_len == 0) {
1998 			return 0;
1999 		}
2000 	}
2001 
2002 	/* 1 */
2003 	if (xmlValidateQName(BAD_CAST qname, 0) != 0) {
2004 		return NAMESPACE_ERR;
2005 	}
2006 
2007 	/* 2 */
2008 	if (*prefix != NULL && uri_len == 0) {
2009 		return NAMESPACE_ERR;
2010 	}
2011 
2012 	return 0;
2013 }
2014 /* }}} */
2015 
2016 /* Creates a new namespace declaration with a random prefix with the given uri on the tree.
2017  * This is used to resolve a namespace prefix conflict in cases where spec does not want a
2018  * namespace error in case of conflicts, but demands a resolution. */
dom_get_ns_resolve_prefix_conflict(xmlNodePtr tree,const char * uri)2019 xmlNsPtr dom_get_ns_resolve_prefix_conflict(xmlNodePtr tree, const char *uri)
2020 {
2021 	ZEND_ASSERT(tree != NULL);
2022 	xmlDocPtr doc = tree->doc;
2023 
2024 	if (UNEXPECTED(doc == NULL)) {
2025 		return NULL;
2026 	}
2027 
2028 	/* Code adapted from libxml2 (2.10.4) */
2029 	char prefix[50];
2030 	int counter = 1;
2031 	snprintf(prefix, sizeof(prefix), "default");
2032 	xmlNsPtr nsptr = xmlSearchNs(doc, tree, (const xmlChar *) prefix);
2033 	while (nsptr != NULL) {
2034 		if (counter > 1000) {
2035 			return NULL;
2036 		}
2037 		snprintf(prefix, sizeof(prefix), "default%d", counter++);
2038 		nsptr = xmlSearchNs(doc, tree, (const xmlChar *) prefix);
2039 	}
2040 
2041 	/* Search yielded no conflict */
2042 	return xmlNewNs(tree, (const xmlChar *) uri, (const xmlChar *) prefix);
2043 }
2044 
2045 /*
2046 http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS
2047 
2048 NAMESPACE_ERR: Raised if
2049 
2050 3. the qualifiedName has a prefix that is "xml" and the namespaceURI is different from "http://www.w3.org/XML/1998/namespace" [XML Namespaces]
2051 4. the qualifiedName or its prefix is "xmlns" and the namespaceURI is different from  "http://www.w3.org/2000/xmlns/"
2052 5. the namespaceURI is "http://www.w3.org/2000/xmlns/" and neither the	qualifiedName nor its prefix is "xmlns".
2053 */
2054 
dom_get_ns_unchecked(xmlNodePtr nodep,char * uri,char * prefix)2055 xmlNsPtr dom_get_ns_unchecked(xmlNodePtr nodep, char *uri, char *prefix)
2056 {
2057 	xmlNsPtr nsptr = xmlNewNs(nodep, BAD_CAST uri, BAD_CAST prefix);
2058 	if (UNEXPECTED(nsptr == NULL)) {
2059 		/* Either memory allocation failure, or it's because of a prefix conflict.
2060 		 * We'll assume a conflict and try again. If it was a memory allocation failure we'll just fail again, whatever.
2061 		 * This isn't needed for every caller (such as createElementNS & DOMElement::__construct), but isn't harmful and simplifies the mental model "when do I use which function?".
2062 		 * This branch will also be taken unlikely anyway as in those cases it'll be for allocation failure. */
2063 		return dom_get_ns_resolve_prefix_conflict(nodep, uri);
2064 	}
2065 
2066 	return nsptr;
2067 }
2068 
2069 /* {{{ xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) */
dom_get_ns(xmlNodePtr nodep,char * uri,int * errorcode,char * prefix)2070 xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) {
2071 	xmlNsPtr nsptr;
2072 
2073 	if (! ((prefix && !strcmp (prefix, "xml") && strcmp(uri, (char *)XML_XML_NAMESPACE)) ||
2074 		   (prefix && !strcmp (prefix, "xmlns") && strcmp(uri, DOM_XMLNS_NS_URI)) ||
2075 		   (prefix && !strcmp(uri, DOM_XMLNS_NS_URI) && strcmp (prefix, "xmlns")))) {
2076 		nsptr = dom_get_ns_unchecked(nodep, uri, prefix);
2077 		if (UNEXPECTED(nsptr == NULL)) {
2078 			goto err;
2079 		}
2080 	} else {
2081 		goto err;
2082 	}
2083 
2084 	*errorcode = 0;
2085 	return nsptr;
2086 err:
2087 	*errorcode = NAMESPACE_ERR;
2088 	return NULL;
2089 }
2090 /* }}} end dom_get_ns */
2091 
2092 /* {{{ xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName) */
dom_get_nsdecl(xmlNode * node,xmlChar * localName)2093 xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName) {
2094 	xmlNsPtr cur;
2095 	xmlNs *ret = NULL;
2096 	if (node == NULL)
2097 		return NULL;
2098 
2099 	if (localName == NULL || localName[0] == '\0') {
2100 		cur = node->nsDef;
2101 		while (cur != NULL) {
2102 			if (cur->prefix == NULL  && cur->href != NULL) {
2103 				ret = cur;
2104 				break;
2105 			}
2106 			cur = cur->next;
2107 		}
2108 	} else {
2109 		cur = node->nsDef;
2110 		while (cur != NULL) {
2111 			if (cur->prefix != NULL && xmlStrEqual(localName, cur->prefix)) {
2112 				ret = cur;
2113 				break;
2114 			}
2115 			cur = cur->next;
2116 		}
2117 	}
2118 	return ret;
2119 }
2120 /* }}} end dom_get_nsdecl */
2121 
php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep,xmlNsPtr original)2122 static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original)
2123 {
2124 	xmlNodePtr attrp;
2125 	xmlNsPtr curns = xmlNewNs(NULL, original->href, NULL);
2126 	if (original->prefix) {
2127 		curns->prefix = xmlStrdup(original->prefix);
2128 		attrp = xmlNewDocNode(nodep->doc, NULL, BAD_CAST original->prefix, original->href);
2129 	} else {
2130 		attrp = xmlNewDocNode(nodep->doc, NULL, BAD_CAST "xmlns", original->href);
2131 	}
2132 	attrp->type = XML_NAMESPACE_DECL;
2133 	attrp->parent = nodep;
2134 	attrp->ns = curns;
2135 	return attrp;
2136 }
2137 
2138 /* Note: Assumes the additional lifetime was already added in the caller. */
php_dom_create_fake_namespace_decl(xmlNodePtr nodep,xmlNsPtr original,zval * return_value,dom_object * parent_intern)2139 xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern)
2140 {
2141 	xmlNodePtr attrp = php_dom_create_fake_namespace_decl_node_ptr(nodep, original);
2142 	php_dom_create_object(attrp, return_value, parent_intern);
2143 	/* This object must exist, because we just created an object for it via php_dom_create_object(). */
2144 	php_dom_namespace_node_obj_from_obj(Z_OBJ_P(return_value))->parent_intern = parent_intern;
2145 	return attrp;
2146 }
2147 
dom_nodemap_or_nodelist_process_offset_as_named(zval * offset,zend_long * lval)2148 static bool dom_nodemap_or_nodelist_process_offset_as_named(zval *offset, zend_long *lval)
2149 {
2150 	if (Z_TYPE_P(offset) == IS_STRING) {
2151 		/* See zval_get_long_func() */
2152 		double dval;
2153 		zend_uchar is_numeric_string_type;
2154 		if (0 == (is_numeric_string_type = is_numeric_string(Z_STRVAL_P(offset), Z_STRLEN_P(offset), lval, &dval, true))) {
2155 			return true;
2156 		} else if (is_numeric_string_type == IS_DOUBLE) {
2157 			*lval = zend_dval_to_lval_cap(dval);
2158 		}
2159 	} else {
2160 		*lval = zval_get_long(offset);
2161 	}
2162 	return false;
2163 }
2164 
dom_nodelist_read_dimension(zend_object * object,zval * offset,int type,zval * rv)2165 static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv)
2166 {
2167 	if (UNEXPECTED(!offset)) {
2168 		zend_throw_error(NULL, "Cannot access %s without offset", ZSTR_VAL(object->ce->name));
2169 		return NULL;
2170 	}
2171 
2172 	ZVAL_DEREF(offset);
2173 
2174 	zend_long lval;
2175 	if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) {
2176 		/* does not support named lookup */
2177 		ZVAL_NULL(rv);
2178 		return rv;
2179 	}
2180 
2181 	php_dom_nodelist_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, lval, rv);
2182 	return rv;
2183 }
2184 
dom_nodelist_has_dimension(zend_object * object,zval * member,int check_empty)2185 static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty)
2186 {
2187 	ZVAL_DEREF(member);
2188 
2189 	/* If it exists, it cannot be empty because nodes aren't empty. */
2190 	ZEND_IGNORE_VALUE(check_empty);
2191 
2192 	zend_long offset;
2193 	if (dom_nodemap_or_nodelist_process_offset_as_named(member, &offset)) {
2194 		/* does not support named lookup */
2195 		return 0;
2196 	}
2197 
2198 	return offset >= 0 && offset < php_dom_get_nodelist_length(php_dom_obj_from_obj(object));
2199 }
2200 
dom_remove_all_children(xmlNodePtr nodep)2201 void dom_remove_all_children(xmlNodePtr nodep)
2202 {
2203 	if (nodep->children) {
2204 		node_list_unlink(nodep->children);
2205 		php_libxml_node_free_list((xmlNodePtr) nodep->children);
2206 		nodep->children = NULL;
2207 		nodep->last = NULL;
2208 	}
2209 }
2210 
php_dom_get_content_into_zval(const xmlNode * nodep,zval * return_value,bool null_on_failure)2211 void php_dom_get_content_into_zval(const xmlNode *nodep, zval *return_value, bool null_on_failure)
2212 {
2213 	ZEND_ASSERT(nodep != NULL);
2214 
2215 	switch (nodep->type) {
2216 		case XML_TEXT_NODE:
2217 		case XML_CDATA_SECTION_NODE:
2218 		case XML_PI_NODE:
2219 		case XML_COMMENT_NODE: {
2220 			char *str = (char * ) nodep->content;
2221 			if (str != NULL) {
2222 				RETURN_STRING(str);
2223 			}
2224 
2225 			break;
2226 		}
2227 
2228 		case XML_ATTRIBUTE_NODE: {
2229 			bool free;
2230 			xmlChar *value = dom_attr_value((const xmlAttr *) nodep, &free);
2231 			RETURN_STRING_FAST((const char *) value);
2232 			if (free) {
2233 				xmlFree(value);
2234 			}
2235 			return;
2236 		}
2237 
2238 		default: {
2239 			char *str = (char *) xmlNodeGetContent(nodep);
2240 			if (str != NULL) {
2241 				RETVAL_STRING(str);
2242 				xmlFree(str);
2243 				return;
2244 			}
2245 
2246 			break;
2247 		}
2248 	}
2249 
2250 	if (null_on_failure) {
2251 		RETURN_NULL();
2252 	} else {
2253 		RETURN_EMPTY_STRING();
2254 	}
2255 }
2256 
dom_nodemap_read_dimension(zend_object * object,zval * offset,int type,zval * rv)2257 static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv)
2258 {
2259 	if (UNEXPECTED(!offset)) {
2260 		zend_throw_error(NULL, "Cannot access %s without offset", ZSTR_VAL(object->ce->name));
2261 		return NULL;
2262 	}
2263 
2264 	ZVAL_DEREF(offset);
2265 
2266 	zend_long lval;
2267 	if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) {
2268 		/* exceptional case, switch to named lookup */
2269 		php_dom_named_node_map_get_named_item_into_zval(php_dom_obj_from_obj(object)->ptr, Z_STR_P(offset), rv);
2270 		return rv;
2271 	}
2272 
2273 	/* see PHP_METHOD(DOMNamedNodeMap, item) */
2274 	if (UNEXPECTED(lval < 0 || ZEND_LONG_INT_OVFL(lval))) {
2275 		zend_value_error("must be between 0 and %d", INT_MAX);
2276 		return NULL;
2277 	}
2278 
2279 	php_dom_named_node_map_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, lval, rv);
2280 	return rv;
2281 }
2282 
dom_nodemap_has_dimension(zend_object * object,zval * member,int check_empty)2283 static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty)
2284 {
2285 	ZVAL_DEREF(member);
2286 
2287 	/* If it exists, it cannot be empty because nodes aren't empty. */
2288 	ZEND_IGNORE_VALUE(check_empty);
2289 
2290 	zend_long offset;
2291 	if (dom_nodemap_or_nodelist_process_offset_as_named(member, &offset)) {
2292 		/* exceptional case, switch to named lookup */
2293 		return php_dom_named_node_map_get_named_item(php_dom_obj_from_obj(object)->ptr, Z_STR_P(member), false) != NULL;
2294 	}
2295 
2296 	return offset >= 0 && offset < php_dom_get_namednodemap_length(php_dom_obj_from_obj(object));
2297 }
2298 
dom_modern_nodemap_read_dimension(zend_object * object,zval * offset,int type,zval * rv)2299 static zval *dom_modern_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv)
2300 {
2301 	if (UNEXPECTED(!offset)) {
2302 		zend_throw_error(NULL, "Cannot append to %s", ZSTR_VAL(object->ce->name));
2303 		return NULL;
2304 	}
2305 
2306 	dom_nnodemap_object *map = php_dom_obj_from_obj(object)->ptr;
2307 
2308 	ZVAL_DEREF(offset);
2309 	if (Z_TYPE_P(offset) == IS_STRING) {
2310 		zend_ulong lval;
2311 		if (ZEND_HANDLE_NUMERIC(Z_STR_P(offset), lval)) {
2312 			php_dom_named_node_map_get_item_into_zval(map, (zend_long) lval, rv);
2313 		} else {
2314 			php_dom_named_node_map_get_named_item_into_zval(map, Z_STR_P(offset), rv);
2315 		}
2316 	} else if (Z_TYPE_P(offset) == IS_LONG) {
2317 		php_dom_named_node_map_get_item_into_zval(map, Z_LVAL_P(offset), rv);
2318 	} else if (Z_TYPE_P(offset) == IS_DOUBLE) {
2319 		php_dom_named_node_map_get_item_into_zval(map, zend_dval_to_lval_safe(Z_DVAL_P(offset)), rv);
2320 	} else {
2321 		zend_illegal_container_offset(object->ce->name, offset, type);
2322 		return NULL;
2323 	}
2324 
2325 	return rv;
2326 }
2327 
dom_modern_nodemap_has_dimension(zend_object * object,zval * member,int check_empty)2328 static int dom_modern_nodemap_has_dimension(zend_object *object, zval *member, int check_empty)
2329 {
2330 	/* If it exists, it cannot be empty because nodes aren't empty. */
2331 	ZEND_IGNORE_VALUE(check_empty);
2332 
2333 	dom_object *obj = php_dom_obj_from_obj(object);
2334 	dom_nnodemap_object *map = obj->ptr;
2335 
2336 	ZVAL_DEREF(member);
2337 	if (Z_TYPE_P(member) == IS_STRING) {
2338 		zend_ulong lval;
2339 		if (ZEND_HANDLE_NUMERIC(Z_STR_P(member), lval)) {
2340 			return (zend_long) lval >= 0 && (zend_long) lval < php_dom_get_namednodemap_length(obj);
2341 		} else {
2342 			return php_dom_named_node_map_get_named_item(map, Z_STR_P(member), false) != NULL;
2343 		}
2344 	} else if (Z_TYPE_P(member) == IS_LONG) {
2345 		zend_long offset = Z_LVAL_P(member);
2346 		return offset >= 0 && offset < php_dom_get_namednodemap_length(obj);
2347 	} else if (Z_TYPE_P(member) == IS_DOUBLE) {
2348 		zend_long offset = zend_dval_to_lval_safe(Z_DVAL_P(member));
2349 		return offset >= 0 && offset < php_dom_get_namednodemap_length(obj);
2350 	} else {
2351 		zend_illegal_container_offset(object->ce->name, member, BP_VAR_IS);
2352 		return 0;
2353 	}
2354 }
2355 
dom_clone_container_helper(php_dom_libxml_ns_mapper * ns_mapper,xmlNodePtr src_node,xmlDocPtr dst_doc)2356 static xmlNodePtr dom_clone_container_helper(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr src_node, xmlDocPtr dst_doc)
2357 {
2358 	xmlNodePtr clone = xmlDocCopyNode(src_node, dst_doc, 0);
2359 	if (EXPECTED(clone != NULL)) {
2360 		/* Set namespace to the original, reconciliation will fix this up. */
2361 		clone->ns = src_node->ns;
2362 
2363 		if (src_node->type == XML_ELEMENT_NODE) {
2364 			xmlAttrPtr last_added_attr = NULL;
2365 
2366 			if (src_node->nsDef != NULL) {
2367 				xmlNsPtr current_ns = src_node->nsDef;
2368 				do {
2369 					php_dom_ns_compat_mark_attribute(ns_mapper, clone, current_ns);
2370 				} while ((current_ns = current_ns->next) != NULL);
2371 
2372 				last_added_attr = clone->properties;
2373 				while (last_added_attr->next != NULL) {
2374 					last_added_attr = last_added_attr->next;
2375 				}
2376 			}
2377 
2378 			/* Attribute cloning logic. */
2379 			for (xmlAttrPtr attr = src_node->properties; attr != NULL; attr = attr->next) {
2380 				xmlAttrPtr new_attr = (xmlAttrPtr) xmlDocCopyNode((xmlNodePtr) attr, dst_doc, 0);
2381 				if (UNEXPECTED(new_attr == NULL)) {
2382 					xmlFreeNode(clone);
2383 					return NULL;
2384 				}
2385 				if (last_added_attr == NULL) {
2386 					clone->properties = new_attr;
2387 				} else {
2388 					new_attr->prev = last_added_attr;
2389 					last_added_attr->next = new_attr;
2390 				}
2391 				new_attr->parent = clone;
2392 				last_added_attr = new_attr;
2393 
2394 				/* Set namespace to the original, reconciliation will fix this up. */
2395 				new_attr->ns = attr->ns;
2396 			}
2397 		}
2398 	}
2399 	return clone;
2400 }
2401 
dom_clone_helper(php_dom_libxml_ns_mapper * ns_mapper,xmlNodePtr src_node,xmlDocPtr dst_doc,bool recursive)2402 static xmlNodePtr dom_clone_helper(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr src_node, xmlDocPtr dst_doc, bool recursive)
2403 {
2404 	xmlNodePtr outer_clone = dom_clone_container_helper(ns_mapper, src_node, dst_doc);
2405 
2406 	if (!recursive || (src_node->type != XML_ELEMENT_NODE && src_node->type != XML_DOCUMENT_FRAG_NODE && src_node->type != XML_DOCUMENT_NODE && src_node->type != XML_HTML_DOCUMENT_NODE)) {
2407 		return outer_clone;
2408 	}
2409 
2410 	/* Handle dtd separately, because it is linked twice and requires a special copy function. */
2411 	if (src_node->type == XML_DOCUMENT_NODE || src_node->type == XML_HTML_DOCUMENT_NODE) {
2412 		dst_doc = (xmlDocPtr) outer_clone;
2413 
2414 		xmlDtdPtr original_subset = ((xmlDocPtr) src_node)->intSubset;
2415 		if (original_subset != NULL) {
2416 			dst_doc->intSubset = xmlCopyDtd(((xmlDocPtr) src_node)->intSubset);
2417 			if (UNEXPECTED(dst_doc->intSubset == NULL)) {
2418 				xmlFreeNode(outer_clone);
2419 				return NULL;
2420 			}
2421 			dst_doc->intSubset->parent = dst_doc;
2422 			xmlSetTreeDoc((xmlNodePtr) dst_doc->intSubset, dst_doc);
2423 			dst_doc->children = dst_doc->last = (xmlNodePtr) dst_doc->intSubset;
2424 		}
2425 	}
2426 
2427 	xmlNodePtr cloned_parent = outer_clone;
2428 	xmlNodePtr base = src_node;
2429 	src_node = src_node->children;
2430 	while (src_node != NULL) {
2431 		ZEND_ASSERT(src_node != base);
2432 
2433 		xmlNodePtr cloned;
2434 		if (src_node->type == XML_ELEMENT_NODE) {
2435 			cloned = dom_clone_container_helper(ns_mapper, src_node, dst_doc);
2436 		} else if (src_node->type == XML_DTD_NODE) {
2437 			/* Already handled. */
2438 			cloned = NULL;
2439 		} else {
2440 			cloned = xmlDocCopyNode(src_node, dst_doc, 1);
2441 		}
2442 
2443 		if (EXPECTED(cloned != NULL)) {
2444 			if (cloned_parent->children == NULL) {
2445 				cloned_parent->children = cloned;
2446 			} else {
2447 				cloned->prev = cloned_parent->last;
2448 				cloned_parent->last->next = cloned;
2449 			}
2450 			cloned->parent = cloned_parent;
2451 			cloned_parent->last = cloned;
2452 		}
2453 
2454 		if (src_node->type == XML_ELEMENT_NODE && src_node->children) {
2455 			cloned_parent = cloned;
2456 			src_node = src_node->children;
2457 		} else if (src_node->next) {
2458 			src_node = src_node->next;
2459 		} else {
2460 			/* Go upwards, until we find a parent node with a next sibling, or until we hit the base. */
2461 			do {
2462 				src_node = src_node->parent;
2463 				if (src_node == base) {
2464 					return outer_clone;
2465 				}
2466 				cloned_parent = cloned_parent->parent;
2467 			} while (src_node->next == NULL);
2468 			src_node = src_node->next;
2469 		}
2470 	}
2471 
2472 	return outer_clone;
2473 }
2474 
dom_clone_node(php_dom_libxml_ns_mapper * ns_mapper,xmlNodePtr node,xmlDocPtr doc,bool recursive)2475 xmlNodePtr dom_clone_node(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node, xmlDocPtr doc, bool recursive)
2476 {
2477 	if (node->type == XML_DTD_NODE) {
2478 		/* The behaviour w.r.t. the internal subset is implementation-defined according to DOM 3.
2479 		 * This follows what e.g. Java and C# do: copy it regardless of the recursiveness.
2480 		 * Makes sense as the subset is not exactly a child in the normal sense. */
2481 		xmlDtdPtr dtd = xmlCopyDtd((xmlDtdPtr) node);
2482 		xmlSetTreeDoc((xmlNodePtr) dtd, doc);
2483 		return (xmlNodePtr) dtd;
2484 	}
2485 
2486 	if (ns_mapper != NULL) {
2487 		xmlNodePtr clone = dom_clone_helper(ns_mapper, node, doc, recursive);
2488 		if (EXPECTED(clone != NULL)) {
2489 			if (clone->type == XML_DOCUMENT_NODE || clone->type == XML_HTML_DOCUMENT_NODE || clone->type == XML_DOCUMENT_FRAG_NODE) {
2490 				for (xmlNodePtr child = clone->children; child != NULL; child = child->next) {
2491 					php_dom_libxml_reconcile_modern(ns_mapper, child);
2492 				}
2493 			} else {
2494 				php_dom_libxml_reconcile_modern(ns_mapper, clone);
2495 			}
2496 		}
2497 		return clone;
2498 	} else {
2499 		/* See http://www.xmlsoft.org/html/libxml-tree.html#xmlDocCopyNode for meaning of values */
2500 		int extended_recursive = recursive;
2501 		if (!recursive && node->type == XML_ELEMENT_NODE) {
2502 			extended_recursive = 2;
2503 		}
2504 		return xmlDocCopyNode(node, doc, extended_recursive);
2505 	}
2506 }
2507 
php_dom_has_child_of_type(xmlNodePtr node,xmlElementType type)2508 bool php_dom_has_child_of_type(xmlNodePtr node, xmlElementType type)
2509 {
2510 	xmlNodePtr child = node->children;
2511 
2512 	while (child != NULL) {
2513 		if (child->type == type) {
2514 			return true;
2515 		}
2516 
2517 		child = child->next;
2518 	}
2519 
2520 	return false;
2521 }
2522 
php_dom_has_sibling_following_node(xmlNodePtr node,xmlElementType type)2523 bool php_dom_has_sibling_following_node(xmlNodePtr node, xmlElementType type)
2524 {
2525 	xmlNodePtr next = node->next;
2526 
2527 	while (next != NULL) {
2528 		if (next->type == type) {
2529 			return true;
2530 		}
2531 
2532 		next = next->next;
2533 	}
2534 
2535 	return false;
2536 }
2537 
php_dom_has_sibling_preceding_node(xmlNodePtr node,xmlElementType type)2538 bool php_dom_has_sibling_preceding_node(xmlNodePtr node, xmlElementType type)
2539 {
2540 	xmlNodePtr prev = node->prev;
2541 
2542 	while (prev != NULL) {
2543 		if (prev->type == type) {
2544 			return true;
2545 		}
2546 
2547 		prev = prev->prev;
2548 	}
2549 
2550 	return false;
2551 }
2552 
php_dom_get_attribute_node(xmlNodePtr elem,const xmlChar * name,size_t name_len)2553 xmlAttrPtr php_dom_get_attribute_node(xmlNodePtr elem, const xmlChar *name, size_t name_len)
2554 {
2555 	xmlChar *name_processed = BAD_CAST name;
2556 	if (php_dom_ns_is_html_and_document_is_html(elem)) {
2557 		char *lowercase_copy = zend_str_tolower_dup_ex((char *) name, name_len);
2558 		if (lowercase_copy != NULL) {
2559 			name_processed = BAD_CAST lowercase_copy;
2560 		}
2561 	}
2562 
2563 	xmlAttrPtr ret = NULL;
2564 	for (xmlAttrPtr attr = elem->properties; attr != NULL; attr = attr->next) {
2565 		if (dom_match_qualified_name_according_to_spec(name_processed, (xmlNodePtr) attr)) {
2566 			ret = attr;
2567 			break;
2568 		}
2569 	}
2570 
2571 	if (name_processed != name) {
2572 		efree(name_processed);
2573 	}
2574 
2575 	return ret;
2576 }
2577 
2578 /* Workaround for a libxml2 bug on Windows: https://gitlab.gnome.org/GNOME/libxml2/-/issues/611. */
php_dom_libxml_fix_file_path(xmlChar * path)2579 xmlChar *php_dom_libxml_fix_file_path(xmlChar *path)
2580 {
2581 	if (strncmp((char *) path, "file:/", sizeof("file:/") - 1) == 0) {
2582 		if (path[6] != '/' && path[6] != '\0' && path[7] != '/' && path[7] != '\0') {
2583 			/* The path is file:/xx... where xx != "//", which is broken */
2584 			xmlChar *new_path = xmlStrdup(BAD_CAST "file:///");
2585 			if (UNEXPECTED(new_path == NULL)) {
2586 				return path;
2587 			}
2588 			new_path = xmlStrcat(new_path, path + 6);
2589 			xmlFree(path);
2590 			return new_path;
2591 		}
2592 	}
2593 	return path;
2594 }
2595 
php_dom_create_html_doc(void)2596 xmlDocPtr php_dom_create_html_doc(void)
2597 {
2598 #ifdef LIBXML_HTML_ENABLED
2599 	xmlDocPtr lxml_doc = htmlNewDocNoDtD(NULL, NULL);
2600 	if (EXPECTED(lxml_doc)) {
2601 		lxml_doc->dict = xmlDictCreate();
2602 	}
2603 #else
2604 	/* If HTML support is not enabled, then htmlNewDocNoDtD() is not available.
2605 	 * This code mimics the behaviour. */
2606 	xmlDocPtr lxml_doc = xmlNewDoc((const xmlChar *) "1.0");
2607 	if (EXPECTED(lxml_doc)) {
2608 		lxml_doc->type = XML_HTML_DOCUMENT_NODE;
2609 		lxml_doc->dict = xmlDictCreate();
2610 	}
2611 #endif
2612 	return lxml_doc;
2613 }
2614 
2615 #endif /* HAVE_DOM */
2616