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