xref: /PHP-8.2/ext/dom/php_dom.c (revision 9fc7be8c)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Christian Stocker <chregu@php.net>                          |
14    |          Rob Richards <rrichards@php.net>                            |
15    |          Marcus Borger <helly@php.net>                               |
16    +----------------------------------------------------------------------+
17 */
18 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include "php.h"
24 #if defined(HAVE_LIBXML) && defined(HAVE_DOM)
25 #include "php_dom.h"
26 #include "php_dom_arginfo.h"
27 #include "dom_properties.h"
28 #include "zend_interfaces.h"
29 #include "lexbor/lexbor/core/types.h"
30 #include "lexbor/lexbor/core/lexbor.h"
31 
32 #include "ext/standard/info.h"
33 
34 /* {{{ class entries */
35 PHP_DOM_EXPORT zend_class_entry *dom_node_class_entry;
36 PHP_DOM_EXPORT zend_class_entry *dom_domexception_class_entry;
37 PHP_DOM_EXPORT zend_class_entry *dom_parentnode_class_entry;
38 PHP_DOM_EXPORT zend_class_entry *dom_childnode_class_entry;
39 PHP_DOM_EXPORT zend_class_entry *dom_domimplementation_class_entry;
40 PHP_DOM_EXPORT zend_class_entry *dom_documentfragment_class_entry;
41 PHP_DOM_EXPORT zend_class_entry *dom_document_class_entry;
42 PHP_DOM_EXPORT zend_class_entry *dom_html_document_class_entry;
43 PHP_DOM_EXPORT zend_class_entry *dom_xml_document_class_entry;
44 PHP_DOM_EXPORT zend_class_entry *dom_nodelist_class_entry;
45 PHP_DOM_EXPORT zend_class_entry *dom_namednodemap_class_entry;
46 PHP_DOM_EXPORT zend_class_entry *dom_characterdata_class_entry;
47 PHP_DOM_EXPORT zend_class_entry *dom_attr_class_entry;
48 PHP_DOM_EXPORT zend_class_entry *dom_element_class_entry;
49 PHP_DOM_EXPORT zend_class_entry *dom_text_class_entry;
50 PHP_DOM_EXPORT zend_class_entry *dom_comment_class_entry;
51 PHP_DOM_EXPORT zend_class_entry *dom_cdatasection_class_entry;
52 PHP_DOM_EXPORT zend_class_entry *dom_documenttype_class_entry;
53 PHP_DOM_EXPORT zend_class_entry *dom_notation_class_entry;
54 PHP_DOM_EXPORT zend_class_entry *dom_entity_class_entry;
55 PHP_DOM_EXPORT zend_class_entry *dom_entityreference_class_entry;
56 PHP_DOM_EXPORT zend_class_entry *dom_processinginstruction_class_entry;
57 PHP_DOM_EXPORT zend_class_entry *dom_abstract_base_document_class_entry;
58 #ifdef LIBXML_XPATH_ENABLED
59 PHP_DOM_EXPORT zend_class_entry *dom_xpath_class_entry;
60 #endif
61 PHP_DOM_EXPORT zend_class_entry *dom_namespace_node_class_entry;
62 /* }}} */
63 
64 zend_object_handlers dom_object_handlers;
65 zend_object_handlers dom_nnodemap_object_handlers;
66 zend_object_handlers dom_nodelist_object_handlers;
67 zend_object_handlers dom_object_namespace_node_handlers;
68 #ifdef LIBXML_XPATH_ENABLED
69 zend_object_handlers dom_xpath_object_handlers;
70 #endif
71 
72 static HashTable classes;
73 /* {{{ prop handler tables */
74 static HashTable dom_document_prop_handlers;
75 static HashTable dom_xml_document_prop_handlers;
76 static HashTable dom_html_document_prop_handlers;
77 static HashTable dom_documentfragment_prop_handlers;
78 static HashTable dom_node_prop_handlers;
79 static HashTable dom_nodelist_prop_handlers;
80 static HashTable dom_namednodemap_prop_handlers;
81 static HashTable dom_characterdata_prop_handlers;
82 static HashTable dom_attr_prop_handlers;
83 static HashTable dom_element_prop_handlers;
84 static HashTable dom_text_prop_handlers;
85 static HashTable dom_documenttype_prop_handlers;
86 static HashTable dom_notation_prop_handlers;
87 static HashTable dom_entity_prop_handlers;
88 static HashTable dom_processinginstruction_prop_handlers;
89 static HashTable dom_namespace_node_prop_handlers;
90 #ifdef LIBXML_XPATH_ENABLED
91 static HashTable dom_xpath_prop_handlers;
92 #endif
93 /* }}} */
94 
95 static zend_object *dom_objects_namespace_node_new(zend_class_entry *class_type);
96 static void dom_object_namespace_node_free_storage(zend_object *object);
97 static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original);
98 
99 typedef zend_result (*dom_read_t)(dom_object *obj, zval *retval);
100 typedef zend_result (*dom_write_t)(dom_object *obj, zval *newval);
101 
102 typedef struct _dom_prop_handler {
103 	dom_read_t read_func;
104 	dom_write_t write_func;
105 } dom_prop_handler;
106 
107 /* {{{ int dom_node_is_read_only(xmlNodePtr node) */
dom_node_is_read_only(xmlNodePtr node)108 int dom_node_is_read_only(xmlNodePtr node) {
109 	switch (node->type) {
110 		case XML_ENTITY_REF_NODE:
111 		case XML_ENTITY_NODE:
112 		case XML_DOCUMENT_TYPE_NODE:
113 		case XML_NOTATION_NODE:
114 		case XML_DTD_NODE:
115 		case XML_ELEMENT_DECL:
116 		case XML_ATTRIBUTE_DECL:
117 		case XML_ENTITY_DECL:
118 		case XML_NAMESPACE_DECL:
119 			return SUCCESS;
120 			break;
121 		default:
122 			if (node->doc == NULL) {
123 				return SUCCESS;
124 			} else {
125 				return FAILURE;
126 			}
127 	}
128 }
129 /* }}} end dom_node_is_read_only */
130 
131 /* {{{ int dom_node_children_valid(xmlNodePtr node) */
dom_node_children_valid(xmlNodePtr node)132 int dom_node_children_valid(xmlNodePtr node) {
133 	switch (node->type) {
134 		case XML_DOCUMENT_TYPE_NODE:
135 		case XML_DTD_NODE:
136 		case XML_PI_NODE:
137 		case XML_COMMENT_NODE:
138 		case XML_TEXT_NODE:
139 		case XML_CDATA_SECTION_NODE:
140 		case XML_NOTATION_NODE:
141 			return FAILURE;
142 			break;
143 		default:
144 			return SUCCESS;
145 	}
146 }
147 /* }}} end dom_node_children_valid */
148 
149 static const libxml_doc_props default_doc_props = {
150 	.formatoutput = false,
151 	.validateonparse = false,
152 	.resolveexternals = false,
153 	.preservewhitespace = true,
154 	.substituteentities = false,
155 	.stricterror = true,
156 	.recover = false,
157 	.classmap = NULL,
158 };
159 
160 /* {{{ dom_get_doc_props() */
dom_get_doc_props(php_libxml_ref_obj * document)161 dom_doc_propsptr dom_get_doc_props(php_libxml_ref_obj *document)
162 {
163 	dom_doc_propsptr doc_props;
164 
165 	if (document && document->doc_props) {
166 		return document->doc_props;
167 	} else {
168 		doc_props = emalloc(sizeof(libxml_doc_props));
169 		memcpy(doc_props, &default_doc_props, sizeof(libxml_doc_props));
170 		if (document) {
171 			document->doc_props = doc_props;
172 		}
173 		return doc_props;
174 	}
175 }
176 /* }}} */
177 
dom_get_doc_props_read_only(const php_libxml_ref_obj * document)178 libxml_doc_props const* dom_get_doc_props_read_only(const php_libxml_ref_obj *document)
179 {
180 	if (document && document->doc_props) {
181 		return document->doc_props;
182 	} else {
183 		return &default_doc_props;
184 	}
185 }
186 
dom_copy_doc_props(php_libxml_ref_obj * source_doc,php_libxml_ref_obj * dest_doc)187 static void dom_copy_doc_props(php_libxml_ref_obj *source_doc, php_libxml_ref_obj *dest_doc)
188 {
189 	dom_doc_propsptr dest;
190 
191 	if (source_doc && dest_doc) {
192 
193 		libxml_doc_props const* source = dom_get_doc_props_read_only(source_doc);
194 		dest = dom_get_doc_props(dest_doc);
195 
196 		dest->formatoutput = source->formatoutput;
197 		dest->validateonparse = source->validateonparse;
198 		dest->resolveexternals = source->resolveexternals;
199 		dest->preservewhitespace = source->preservewhitespace;
200 		dest->substituteentities = source->substituteentities;
201 		dest->stricterror = source->stricterror;
202 		dest->recover = source->recover;
203 		if (source->classmap) {
204 			ALLOC_HASHTABLE(dest->classmap);
205 			zend_hash_init(dest->classmap, 0, NULL, NULL, false);
206 			zend_hash_copy(dest->classmap, source->classmap, NULL);
207 		}
208 
209 		dest_doc->is_modern_api_class = source_doc->is_modern_api_class;
210 	}
211 }
212 
dom_set_doc_classmap(php_libxml_ref_obj * document,zend_class_entry * basece,zend_class_entry * ce)213 void dom_set_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece, zend_class_entry *ce)
214 {
215 	dom_doc_propsptr doc_props;
216 
217 	if (document) {
218 		doc_props = dom_get_doc_props(document);
219 		if (doc_props->classmap == NULL) {
220 			if (ce == NULL) {
221 				return;
222 			}
223 			ALLOC_HASHTABLE(doc_props->classmap);
224 			zend_hash_init(doc_props->classmap, 0, NULL, NULL, false);
225 		}
226 		if (ce) {
227 			zend_hash_update_ptr(doc_props->classmap, basece->name, ce);
228 		} else {
229 			zend_hash_del(doc_props->classmap, basece->name);
230 		}
231 	}
232 }
233 
dom_get_doc_classmap(php_libxml_ref_obj * document,zend_class_entry * basece)234 zend_class_entry *dom_get_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece)
235 {
236 	if (document) {
237 		libxml_doc_props const* doc_props = dom_get_doc_props_read_only(document);
238 		if (doc_props->classmap) {
239 			zend_class_entry *ce = zend_hash_find_ptr(doc_props->classmap, basece->name);
240 			if (ce) {
241 				return ce;
242 			}
243 		}
244 	}
245 
246 	return basece;
247 }
248 /* }}} */
249 
250 /* {{{ dom_get_strict_error() */
dom_get_strict_error(php_libxml_ref_obj * document)251 int dom_get_strict_error(php_libxml_ref_obj *document) {
252 	return dom_get_doc_props_read_only(document)->stricterror;
253 }
254 /* }}} */
255 
256 /* {{{ xmlNodePtr dom_object_get_node(dom_object *obj) */
dom_object_get_node(dom_object * obj)257 PHP_DOM_EXPORT xmlNodePtr dom_object_get_node(dom_object *obj)
258 {
259 	if (obj && obj->ptr != NULL) {
260 		return ((php_libxml_node_ptr *)obj->ptr)->node;
261 	} else {
262 		return NULL;
263 	}
264 }
265 /* }}} end dom_object_get_node */
266 
267 /* {{{ dom_object *php_dom_object_get_data(xmlNodePtr obj) */
php_dom_object_get_data(xmlNodePtr obj)268 PHP_DOM_EXPORT dom_object *php_dom_object_get_data(xmlNodePtr obj)
269 {
270 	if (obj && obj->_private != NULL) {
271 		return (dom_object *) ((php_libxml_node_ptr *) obj->_private)->_private;
272 	} else {
273 		return NULL;
274 	}
275 }
276 /* }}} end php_dom_object_get_data */
277 
dom_register_prop_handler(HashTable * prop_handler,const char * name,size_t name_len,const dom_prop_handler * hnd)278 static void dom_register_prop_handler(HashTable *prop_handler, const char *name, size_t name_len, const dom_prop_handler *hnd)
279 {
280 	zend_string *str = zend_string_init_interned(name, name_len, true);
281 	zend_hash_add_new_ptr(prop_handler, str, (void *) hnd);
282 	zend_string_release_ex(str, true);
283 }
284 
285 #define DOM_REGISTER_PROP_HANDLER(prop_handler, name, prop_read_func, prop_write_func) do { \
286 		static const dom_prop_handler hnd = {.read_func = prop_read_func, .write_func = prop_write_func}; \
287 		dom_register_prop_handler(prop_handler, "" name, sizeof("" name) - 1, &hnd); \
288 	} while (0)
289 
dom_get_property_ptr_ptr(zend_object * object,zend_string * name,int type,void ** cache_slot)290 static zval *dom_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot)
291 {
292 	dom_object *obj = php_dom_obj_from_obj(object);
293 
294 	if (!obj->prop_handler || !zend_hash_exists(obj->prop_handler, name)) {
295 		return zend_std_get_property_ptr_ptr(object, name, type, cache_slot);
296 	}
297 
298 	return NULL;
299 }
300 
301 /* {{{ dom_read_property */
dom_read_property(zend_object * object,zend_string * name,int type,void ** cache_slot,zval * rv)302 zval *dom_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv)
303 {
304 	dom_object *obj = php_dom_obj_from_obj(object);
305 	zval *retval;
306 	dom_prop_handler *hnd = NULL;
307 
308 	if (obj->prop_handler != NULL) {
309 		hnd = zend_hash_find_ptr(obj->prop_handler, name);
310 	}
311 
312 	if (hnd) {
313 		int ret = hnd->read_func(obj, rv);
314 		if (ret == SUCCESS) {
315 			retval = rv;
316 		} else {
317 			retval = &EG(uninitialized_zval);
318 		}
319 	} else {
320 		retval = zend_std_read_property(object, name, type, cache_slot, rv);
321 	}
322 
323 	return retval;
324 }
325 /* }}} */
326 
dom_write_property(zend_object * object,zend_string * name,zval * value,void ** cache_slot)327 zval *dom_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot)
328 {
329 	dom_object *obj = php_dom_obj_from_obj(object);
330 	dom_prop_handler *hnd = NULL;
331 
332 	if (obj->prop_handler != NULL) {
333 		hnd = zend_hash_find_ptr(obj->prop_handler, name);
334 	}
335 
336 	if (hnd) {
337 		if (!hnd->write_func) {
338 			zend_throw_error(NULL, "Cannot write read-only property %s::$%s", ZSTR_VAL(object->ce->name), ZSTR_VAL(name));
339 			return &EG(error_zval);
340 		}
341 
342 		zend_property_info *prop = zend_get_property_info(object->ce, name, /* silent */ true);
343 		if (prop && ZEND_TYPE_IS_SET(prop->type)) {
344 			zval tmp;
345 			ZVAL_COPY(&tmp, value);
346 			if (!zend_verify_property_type(prop, &tmp, ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data)))) {
347 				zval_ptr_dtor(&tmp);
348 				return &EG(error_zval);
349 			}
350 			hnd->write_func(obj, &tmp);
351 			zval_ptr_dtor(&tmp);
352 		} else {
353 			hnd->write_func(obj, value);
354 		}
355 
356 		return value;
357 	}
358 
359 	return zend_std_write_property(object, name, value, cache_slot);
360 }
361 
362 /* {{{ dom_property_exists */
dom_property_exists(zend_object * object,zend_string * name,int check_empty,void ** cache_slot)363 static int dom_property_exists(zend_object *object, zend_string *name, int check_empty, void **cache_slot)
364 {
365 	dom_object *obj = php_dom_obj_from_obj(object);
366 	dom_prop_handler *hnd = NULL;
367 	int retval = 0;
368 
369 	if (obj->prop_handler != NULL) {
370 		hnd = zend_hash_find_ptr(obj->prop_handler, name);
371 	}
372 	if (hnd) {
373 		zval tmp;
374 
375 		if (check_empty == 2) {
376 			retval = 1;
377 		} else if (hnd->read_func(obj, &tmp) == SUCCESS) {
378 			if (check_empty == 1) {
379 				retval = zend_is_true(&tmp);
380 			} else if (check_empty == 0) {
381 				retval = (Z_TYPE(tmp) != IS_NULL);
382 			}
383 			zval_ptr_dtor(&tmp);
384 		}
385 	} else {
386 		retval = zend_std_has_property(object, name, check_empty, cache_slot);
387 	}
388 
389 	return retval;
390 }
391 /* }}} */
392 
dom_get_debug_info_helper(zend_object * object,int * is_temp)393 static HashTable* dom_get_debug_info_helper(zend_object *object, int *is_temp) /* {{{ */
394 {
395 	dom_object			*obj = php_dom_obj_from_obj(object);
396 	HashTable			*debug_info,
397 						*prop_handlers = obj->prop_handler,
398 						*std_props;
399 	zend_string			*string_key;
400 	dom_prop_handler	*entry;
401 	zend_string         *object_str;
402 
403 	*is_temp = 1;
404 
405 	std_props = zend_std_get_properties(object);
406 	debug_info = zend_array_dup(std_props);
407 
408 	if (!prop_handlers) {
409 		return debug_info;
410 	}
411 
412 	object_str = ZSTR_INIT_LITERAL("(object value omitted)", false);
413 
414 	ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(prop_handlers, string_key, entry) {
415 		zval value;
416 
417 		ZEND_ASSERT(string_key != NULL);
418 
419 		if (entry->read_func(obj, &value) == FAILURE) {
420 			continue;
421 		}
422 
423 		if (Z_TYPE(value) == IS_OBJECT) {
424 			zval_ptr_dtor(&value);
425 			ZVAL_NEW_STR(&value, object_str);
426 			zend_string_addref(object_str);
427 		}
428 
429 		zend_hash_update(debug_info, string_key, &value);
430 	} ZEND_HASH_FOREACH_END();
431 
432 	zend_string_release_ex(object_str, false);
433 
434 	return debug_info;
435 }
436 /* }}} */
437 
dom_get_debug_info(zend_object * object,int * is_temp)438 static HashTable* dom_get_debug_info(zend_object *object, int *is_temp) /* {{{ */
439 {
440 	return dom_get_debug_info_helper(object, is_temp);
441 }
442 /* }}} */
443 
php_dom_export_node(zval * object)444 void *php_dom_export_node(zval *object) /* {{{ */
445 {
446 	php_libxml_node_object *intern;
447 	xmlNodePtr nodep = NULL;
448 
449 	intern = (php_libxml_node_object *) Z_DOMOBJ_P(object);
450 	if (intern->node) {
451 		nodep = intern->node->node;
452 	}
453 
454 	return nodep;
455 }
456 /* }}} */
457 
458 /* {{{ Get a simplexml_element object from dom to allow for processing */
PHP_FUNCTION(dom_import_simplexml)459 PHP_FUNCTION(dom_import_simplexml)
460 {
461 	zval *node;
462 	xmlNodePtr nodep = NULL;
463 	php_libxml_node_object *nodeobj;
464 	int ret;
465 
466 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "o", &node) == FAILURE) {
467 		RETURN_THROWS();
468 	}
469 
470 	nodeobj = (php_libxml_node_object *) ((char *) Z_OBJ_P(node) - Z_OBJ_HT_P(node)->offset);
471 	nodep = php_libxml_import_node(node);
472 
473 	if (nodep && nodeobj && (nodep->type == XML_ELEMENT_NODE || nodep->type == XML_ATTRIBUTE_NODE)) {
474 		DOM_RET_OBJ((xmlNodePtr) nodep, &ret, (dom_object *)nodeobj);
475 	} else {
476 		zend_argument_value_error(1, "is not a valid node type");
477 		RETURN_THROWS();
478 	}
479 }
480 /* }}} */
481 
482 static dom_object* dom_objects_set_class(zend_class_entry *class_type);
483 
dom_update_refcount_after_clone(dom_object * original,xmlNodePtr original_node,dom_object * clone,xmlNodePtr cloned_node)484 static void dom_update_refcount_after_clone(dom_object *original, xmlNodePtr original_node, dom_object *clone, xmlNodePtr cloned_node)
485 {
486 	/* If we cloned a document then we must create new doc proxy */
487 	if (cloned_node->doc == original_node->doc) {
488 		clone->document = original->document;
489 	}
490 	php_libxml_increment_doc_ref((php_libxml_node_object *)clone, cloned_node->doc);
491 	php_libxml_increment_node_ptr((php_libxml_node_object *)clone, cloned_node, (void *)clone);
492 	if (original->document != clone->document) {
493 		dom_copy_doc_props(original->document, clone->document);
494 		/* Workaround libxml2 bug, see https://gitlab.gnome.org/GNOME/libxml2/-/commit/07920b4381873187c02df53fa9b5d44aff3a7041 */
495 #if LIBXML_VERSION < 20911
496 		if (original_node->type == XML_HTML_DOCUMENT_NODE) {
497 			cloned_node->type = XML_HTML_DOCUMENT_NODE;
498 		}
499 #endif
500 	}
501 }
502 
dom_objects_store_clone_obj(zend_object * zobject)503 static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */
504 {
505 	dom_object *intern = php_dom_obj_from_obj(zobject);
506 	dom_object *clone = dom_objects_set_class(intern->std.ce);
507 
508 	clone->std.handlers = &dom_object_handlers;
509 
510 	if (instanceof_function(intern->std.ce, dom_node_class_entry)) {
511 		xmlNodePtr node = (xmlNodePtr)dom_object_get_node(intern);
512 		if (node != NULL) {
513 			xmlNodePtr cloned_node = xmlDocCopyNode(node, node->doc, 1);
514 			if (cloned_node != NULL) {
515 				dom_update_refcount_after_clone(intern, node, clone, cloned_node);
516 			}
517 
518 		}
519 	}
520 
521 	zend_objects_clone_members(&clone->std, &intern->std);
522 
523 	return &clone->std;
524 }
525 /* }}} */
526 
dom_object_namespace_node_clone_obj(zend_object * zobject)527 static zend_object *dom_object_namespace_node_clone_obj(zend_object *zobject)
528 {
529 	dom_object_namespace_node *intern = php_dom_namespace_node_obj_from_obj(zobject);
530 	zend_object *clone = dom_objects_namespace_node_new(intern->dom.std.ce);
531 	dom_object_namespace_node *clone_intern = php_dom_namespace_node_obj_from_obj(clone);
532 
533 	xmlNodePtr original_node = dom_object_get_node(&intern->dom);
534 	ZEND_ASSERT(original_node->type == XML_NAMESPACE_DECL);
535 	xmlNodePtr cloned_node = php_dom_create_fake_namespace_decl_node_ptr(original_node->parent, original_node->ns);
536 
537 	if (intern->parent_intern) {
538 		clone_intern->parent_intern = intern->parent_intern;
539 		GC_ADDREF(&clone_intern->parent_intern->std);
540 	}
541 	dom_update_refcount_after_clone(&intern->dom, original_node, &clone_intern->dom, cloned_node);
542 
543 	zend_objects_clone_members(clone, &intern->dom.std);
544 	return clone;
545 }
546 
547 static const zend_module_dep dom_deps[] = {
548 	ZEND_MOD_REQUIRED("libxml")
549 	ZEND_MOD_CONFLICTS("domxml")
550 	ZEND_MOD_END
551 };
552 
553 zend_module_entry dom_module_entry = { /* {{{ */
554 	STANDARD_MODULE_HEADER_EX, NULL,
555 	dom_deps,
556 	"dom",
557 	ext_functions,
558 	PHP_MINIT(dom),
559 	PHP_MSHUTDOWN(dom),
560 	NULL,
561 	NULL,
562 	PHP_MINFO(dom),
563 	DOM_API_VERSION, /* Extension versionnumber */
564 	STANDARD_MODULE_PROPERTIES
565 };
566 /* }}} */
567 
568 #ifdef COMPILE_DL_DOM
569 ZEND_GET_MODULE(dom)
570 #endif
571 
572 void dom_objects_free_storage(zend_object *object);
573 void dom_nnodemap_objects_free_storage(zend_object *object);
574 static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
575 static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty);
576 static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
577 static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty);
578 static zend_object *dom_objects_store_clone_obj(zend_object *zobject);
579 
580 #ifdef LIBXML_XPATH_ENABLED
581 void dom_xpath_objects_free_storage(zend_object *object);
582 HashTable *dom_xpath_get_gc(zend_object *object, zval **table, int *n);
583 #endif
584 
dom_malloc(size_t size)585 static void *dom_malloc(size_t size) {
586 	return emalloc(size);
587 }
588 
dom_realloc(void * dst,size_t size)589 static void *dom_realloc(void *dst, size_t size) {
590 	return erealloc(dst, size);
591 }
592 
dom_calloc(size_t num,size_t size)593 static void *dom_calloc(size_t num, size_t size) {
594 	return ecalloc(num, size);
595 }
596 
dom_free(void * ptr)597 static void dom_free(void *ptr) {
598 	efree(ptr);
599 }
600 
register_nondeprecated_xml_props(HashTable * table)601 static void register_nondeprecated_xml_props(HashTable *table)
602 {
603 	DOM_REGISTER_PROP_HANDLER(table, "encoding", dom_document_encoding_read, dom_document_encoding_write);
604 	DOM_REGISTER_PROP_HANDLER(table, "xmlEncoding", dom_document_encoding_read, NULL);
605 	DOM_REGISTER_PROP_HANDLER(table, "standalone", dom_document_standalone_read, dom_document_standalone_write);
606 	DOM_REGISTER_PROP_HANDLER(table, "xmlStandalone", dom_document_standalone_read, dom_document_standalone_write);
607 	DOM_REGISTER_PROP_HANDLER(table, "version", dom_document_version_read, dom_document_version_write);
608 	DOM_REGISTER_PROP_HANDLER(table, "xmlVersion", dom_document_version_read, dom_document_version_write);
609 	DOM_REGISTER_PROP_HANDLER(table, "formatOutput", dom_document_format_output_read, dom_document_format_output_write);
610 	DOM_REGISTER_PROP_HANDLER(table, "validateOnParse", dom_document_validate_on_parse_read, dom_document_validate_on_parse_write);
611 	DOM_REGISTER_PROP_HANDLER(table, "resolveExternals", dom_document_resolve_externals_read, dom_document_resolve_externals_write);
612 	DOM_REGISTER_PROP_HANDLER(table, "preserveWhiteSpace", dom_document_preserve_whitespace_read, dom_document_preserve_whitespace_write);
613 	DOM_REGISTER_PROP_HANDLER(table, "recover", dom_document_recover_read, dom_document_recover_write);
614 	DOM_REGISTER_PROP_HANDLER(table, "substituteEntities", dom_document_substitue_entities_read, dom_document_substitue_entities_write);
615 }
616 
617 /* {{{ PHP_MINIT_FUNCTION(dom) */
PHP_MINIT_FUNCTION(dom)618 PHP_MINIT_FUNCTION(dom)
619 {
620 	memcpy(&dom_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
621 	dom_object_handlers.offset = XtOffsetOf(dom_object, std);
622 	dom_object_handlers.free_obj = dom_objects_free_storage;
623 	dom_object_handlers.read_property = dom_read_property;
624 	dom_object_handlers.write_property = dom_write_property;
625 	dom_object_handlers.get_property_ptr_ptr = dom_get_property_ptr_ptr;
626 	dom_object_handlers.clone_obj = dom_objects_store_clone_obj;
627 	dom_object_handlers.has_property = dom_property_exists;
628 	dom_object_handlers.get_debug_info = dom_get_debug_info;
629 
630 	memcpy(&dom_nnodemap_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
631 	dom_nnodemap_object_handlers.free_obj = dom_nnodemap_objects_free_storage;
632 	dom_nnodemap_object_handlers.read_dimension = dom_nodemap_read_dimension;
633 	dom_nnodemap_object_handlers.has_dimension = dom_nodemap_has_dimension;
634 
635 	memcpy(&dom_nodelist_object_handlers, &dom_nnodemap_object_handlers, sizeof(zend_object_handlers));
636 	dom_nodelist_object_handlers.read_dimension = dom_nodelist_read_dimension;
637 	dom_nodelist_object_handlers.has_dimension = dom_nodelist_has_dimension;
638 
639 	memcpy(&dom_object_namespace_node_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
640 	dom_object_namespace_node_handlers.offset = XtOffsetOf(dom_object_namespace_node, dom.std);
641 	dom_object_namespace_node_handlers.free_obj = dom_object_namespace_node_free_storage;
642 	dom_object_namespace_node_handlers.clone_obj = dom_object_namespace_node_clone_obj;
643 
644 	zend_hash_init(&classes, 0, NULL, NULL, true);
645 
646 	dom_domexception_class_entry = register_class_DOMException(zend_ce_exception);
647 
648 	dom_parentnode_class_entry = register_class_DOMParentNode();
649 
650 	dom_childnode_class_entry = register_class_DOMChildNode();
651 
652 	dom_domimplementation_class_entry = register_class_DOMImplementation();
653 	dom_domimplementation_class_entry->create_object = dom_objects_new;
654 
655 	dom_node_class_entry = register_class_DOMNode();
656 	dom_node_class_entry->create_object = dom_objects_new;
657 
658 	zend_hash_init(&dom_node_prop_handlers, 0, NULL, NULL, true);
659 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "nodeName", dom_node_node_name_read, NULL);
660 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "nodeValue", dom_node_node_value_read, dom_node_node_value_write);
661 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "nodeType", dom_node_node_type_read, NULL);
662 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "parentNode", dom_node_parent_node_read, NULL);
663 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "parentElement", dom_node_parent_element_read, NULL);
664 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "childNodes", dom_node_child_nodes_read, NULL);
665 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "firstChild", dom_node_first_child_read, NULL);
666 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "lastChild", dom_node_last_child_read, NULL);
667 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "previousSibling", dom_node_previous_sibling_read, NULL);
668 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "nextSibling", dom_node_next_sibling_read, NULL);
669 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "attributes", dom_node_attributes_read, NULL);
670 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "isConnected", dom_node_is_connected_read, NULL);
671 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "ownerDocument", dom_node_owner_document_read, NULL);
672 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "namespaceURI", dom_node_namespace_uri_read, NULL);
673 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "prefix", dom_node_prefix_read, dom_node_prefix_write);
674 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "localName", dom_node_local_name_read, NULL);
675 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "baseURI", dom_node_base_uri_read, NULL);
676 	DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write);
677 	zend_hash_add_new_ptr(&classes, dom_node_class_entry->name, &dom_node_prop_handlers);
678 
679 	dom_namespace_node_class_entry = register_class_DOMNameSpaceNode();
680 	dom_namespace_node_class_entry->create_object = dom_objects_namespace_node_new;
681 
682 	zend_hash_init(&dom_namespace_node_prop_handlers, 0, NULL, NULL, true);
683 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "nodeName", dom_node_node_name_read, NULL);
684 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "nodeValue", dom_node_node_value_read, NULL);
685 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "nodeType", dom_node_node_type_read, NULL);
686 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "prefix", dom_node_prefix_read, NULL);
687 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "localName", dom_node_local_name_read, NULL);
688 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "namespaceURI", dom_node_namespace_uri_read, NULL);
689 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "isConnected", dom_node_is_connected_read, NULL);
690 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "ownerDocument", dom_node_owner_document_read, NULL);
691 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "parentNode", dom_node_parent_node_read, NULL);
692 	DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "parentElement", dom_node_parent_element_read, NULL);
693 	zend_hash_add_new_ptr(&classes, dom_namespace_node_class_entry->name, &dom_namespace_node_prop_handlers);
694 
695 	dom_documentfragment_class_entry = register_class_DOMDocumentFragment(dom_node_class_entry, dom_parentnode_class_entry);
696 	dom_documentfragment_class_entry->create_object = dom_objects_new;
697 	zend_hash_init(&dom_documentfragment_prop_handlers, 0, NULL, NULL, true);
698 
699 	DOM_REGISTER_PROP_HANDLER(&dom_documentfragment_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
700 	DOM_REGISTER_PROP_HANDLER(&dom_documentfragment_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
701 	DOM_REGISTER_PROP_HANDLER(&dom_documentfragment_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
702 
703 	zend_hash_merge(&dom_documentfragment_prop_handlers, &dom_node_prop_handlers, NULL, false);
704 	zend_hash_add_new_ptr(&classes, dom_documentfragment_class_entry->name, &dom_documentfragment_prop_handlers);
705 
706 	dom_abstract_base_document_class_entry = register_class_DOM_Document(dom_node_class_entry, dom_parentnode_class_entry);
707 	/* No need to set create_object as it's abstract. */
708 	HashTable dom_abstract_base_document_prop_handlers;
709 	zend_hash_init(&dom_abstract_base_document_prop_handlers, 0, NULL, NULL, true);
710 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "doctype", dom_document_doctype_read, NULL);
711 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "documentElement", dom_document_document_element_read, NULL);
712 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "strictErrorChecking", dom_document_strict_error_checking_read, dom_document_strict_error_checking_write);
713 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "documentURI", dom_document_document_uri_read, dom_document_document_uri_write);
714 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
715 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
716 	DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
717 	zend_hash_merge(&dom_abstract_base_document_prop_handlers, &dom_node_prop_handlers, NULL, false);
718 	/* No need to register in &classes, because this is only used for merging. This is destroyed down below. */
719 
720 	dom_document_class_entry = register_class_DOMDocument(dom_abstract_base_document_class_entry);
721 	dom_document_class_entry->create_object = dom_objects_new;
722 	zend_hash_init(&dom_document_prop_handlers, 0, NULL, NULL, true);
723 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "implementation", dom_document_implementation_read, NULL);
724 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "actualEncoding", dom_document_encoding_read, NULL);
725 	DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "config", dom_document_config_read, NULL);
726 	register_nondeprecated_xml_props(&dom_document_prop_handlers);
727 
728 	zend_hash_merge(&dom_document_prop_handlers, &dom_abstract_base_document_prop_handlers, NULL, false);
729 	zend_hash_add_new_ptr(&classes, dom_document_class_entry->name, &dom_document_prop_handlers);
730 
731 	dom_html_document_class_entry = register_class_DOM_HTMLDocument(dom_abstract_base_document_class_entry);
732 	dom_document_class_entry->create_object = dom_objects_new;
733 	zend_hash_init(&dom_html_document_prop_handlers, 0, NULL, NULL, true);
734 	DOM_REGISTER_PROP_HANDLER(&dom_html_document_prop_handlers, "encoding", dom_document_encoding_read, dom_html_document_encoding_write);
735 
736 	zend_hash_merge(&dom_html_document_prop_handlers, &dom_abstract_base_document_prop_handlers, NULL, false);
737 	zend_hash_add_new_ptr(&classes, dom_html_document_class_entry->name, &dom_html_document_prop_handlers);
738 
739 	dom_xml_document_class_entry = register_class_DOM_XMLDocument(dom_abstract_base_document_class_entry);
740 	dom_xml_document_class_entry->create_object = dom_objects_new;
741 	zend_hash_init(&dom_xml_document_prop_handlers, 0, NULL, NULL, true);
742 	register_nondeprecated_xml_props(&dom_xml_document_prop_handlers);
743 
744 	zend_hash_merge(&dom_xml_document_prop_handlers, &dom_abstract_base_document_prop_handlers, NULL, false);
745 	zend_hash_add_new_ptr(&classes, dom_xml_document_class_entry->name, &dom_xml_document_prop_handlers);
746 
747 	zend_hash_destroy(&dom_abstract_base_document_prop_handlers);
748 
749 	dom_nodelist_class_entry = register_class_DOMNodeList(zend_ce_aggregate, zend_ce_countable);
750 	dom_nodelist_class_entry->create_object = dom_nnodemap_objects_new;
751 	dom_nodelist_class_entry->default_object_handlers = &dom_nodelist_object_handlers;
752 	dom_nodelist_class_entry->get_iterator = php_dom_get_iterator;
753 
754 	zend_hash_init(&dom_nodelist_prop_handlers, 0, NULL, NULL, true);
755 	DOM_REGISTER_PROP_HANDLER(&dom_nodelist_prop_handlers, "length", dom_nodelist_length_read, NULL);
756 	zend_hash_add_new_ptr(&classes, dom_nodelist_class_entry->name, &dom_nodelist_prop_handlers);
757 
758 	dom_namednodemap_class_entry = register_class_DOMNamedNodeMap(zend_ce_aggregate, zend_ce_countable);
759 	dom_namednodemap_class_entry->create_object = dom_nnodemap_objects_new;
760 	dom_namednodemap_class_entry->default_object_handlers = &dom_nnodemap_object_handlers;
761 	dom_namednodemap_class_entry->get_iterator = php_dom_get_iterator;
762 
763 	zend_hash_init(&dom_namednodemap_prop_handlers, 0, NULL, NULL, true);
764 	DOM_REGISTER_PROP_HANDLER(&dom_namednodemap_prop_handlers, "length", dom_namednodemap_length_read, NULL);
765 	zend_hash_add_new_ptr(&classes, dom_namednodemap_class_entry->name, &dom_namednodemap_prop_handlers);
766 
767 	dom_characterdata_class_entry = register_class_DOMCharacterData(dom_node_class_entry, dom_childnode_class_entry);
768 	dom_characterdata_class_entry->create_object = dom_objects_new;
769 
770 	zend_hash_init(&dom_characterdata_prop_handlers, 0, NULL, NULL, true);
771 	DOM_REGISTER_PROP_HANDLER(&dom_characterdata_prop_handlers, "data", dom_characterdata_data_read, dom_characterdata_data_write);
772 	DOM_REGISTER_PROP_HANDLER(&dom_characterdata_prop_handlers, "length", dom_characterdata_length_read, NULL);
773 	DOM_REGISTER_PROP_HANDLER(&dom_characterdata_prop_handlers, "previousElementSibling", dom_node_previous_element_sibling_read, NULL);
774 	DOM_REGISTER_PROP_HANDLER(&dom_characterdata_prop_handlers, "nextElementSibling", dom_node_next_element_sibling_read, NULL);
775 	zend_hash_merge(&dom_characterdata_prop_handlers, &dom_node_prop_handlers, NULL, false);
776 	zend_hash_add_new_ptr(&classes, dom_characterdata_class_entry->name, &dom_characterdata_prop_handlers);
777 
778 	dom_attr_class_entry = register_class_DOMAttr(dom_node_class_entry);
779 	dom_attr_class_entry->create_object = dom_objects_new;
780 
781 	zend_hash_init(&dom_attr_prop_handlers, 0, NULL, NULL, true);
782 	DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "name", dom_attr_name_read, NULL);
783 	DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "specified", dom_attr_specified_read, NULL);
784 	DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "value", dom_attr_value_read, dom_attr_value_write);
785 	DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "ownerElement", dom_attr_owner_element_read, NULL);
786 	DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "schemaTypeInfo", dom_attr_schema_type_info_read, NULL);
787 	zend_hash_merge(&dom_attr_prop_handlers, &dom_node_prop_handlers, NULL, false);
788 	zend_hash_add_new_ptr(&classes, dom_attr_class_entry->name, &dom_attr_prop_handlers);
789 
790 	dom_element_class_entry = register_class_DOMElement(dom_node_class_entry, dom_parentnode_class_entry, dom_childnode_class_entry);
791 	dom_element_class_entry->create_object = dom_objects_new;
792 
793 	zend_hash_init(&dom_element_prop_handlers, 0, NULL, NULL, true);
794 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "tagName", dom_element_tag_name_read, NULL);
795 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "className", dom_element_class_name_read, dom_element_class_name_write);
796 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "id", dom_element_id_read, dom_element_id_write);
797 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "schemaTypeInfo", dom_element_schema_type_info_read, NULL);
798 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
799 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
800 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
801 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "previousElementSibling", dom_node_previous_element_sibling_read, NULL);
802 	DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "nextElementSibling", dom_node_next_element_sibling_read, NULL);
803 	zend_hash_merge(&dom_element_prop_handlers, &dom_node_prop_handlers, NULL, false);
804 	zend_hash_add_new_ptr(&classes, dom_element_class_entry->name, &dom_element_prop_handlers);
805 
806 	dom_text_class_entry = register_class_DOMText(dom_characterdata_class_entry);
807 	dom_text_class_entry->create_object = dom_objects_new;
808 
809 	zend_hash_init(&dom_text_prop_handlers, 0, NULL, NULL, true);
810 	DOM_REGISTER_PROP_HANDLER(&dom_text_prop_handlers, "wholeText", dom_text_whole_text_read, NULL);
811 	zend_hash_merge(&dom_text_prop_handlers, &dom_characterdata_prop_handlers, NULL, false);
812 	zend_hash_add_new_ptr(&classes, dom_text_class_entry->name, &dom_text_prop_handlers);
813 
814 	dom_comment_class_entry = register_class_DOMComment(dom_characterdata_class_entry);
815 	dom_comment_class_entry->create_object = dom_objects_new;
816 	zend_hash_add_new_ptr(&classes, dom_comment_class_entry->name, &dom_characterdata_prop_handlers);
817 
818 	dom_cdatasection_class_entry = register_class_DOMCdataSection(dom_text_class_entry);
819 	dom_cdatasection_class_entry->create_object = dom_objects_new;
820 	zend_hash_add_new_ptr(&classes, dom_cdatasection_class_entry->name, &dom_text_prop_handlers);
821 
822 	dom_documenttype_class_entry = register_class_DOMDocumentType(dom_node_class_entry);
823 	dom_documenttype_class_entry->create_object = dom_objects_new;
824 
825 	zend_hash_init(&dom_documenttype_prop_handlers, 0, NULL, NULL, true);
826 	DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "name", dom_documenttype_name_read, NULL);
827 	DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "entities", dom_documenttype_entities_read, NULL);
828 	DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "notations", dom_documenttype_notations_read, NULL);
829 	DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "publicId", dom_documenttype_public_id_read, NULL);
830 	DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "systemId", dom_documenttype_system_id_read, NULL);
831 	DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "internalSubset", dom_documenttype_internal_subset_read, NULL);
832 	zend_hash_merge(&dom_documenttype_prop_handlers, &dom_node_prop_handlers, NULL, false);
833 	zend_hash_add_new_ptr(&classes, dom_documenttype_class_entry->name, &dom_documenttype_prop_handlers);
834 
835 	dom_notation_class_entry = register_class_DOMNotation(dom_node_class_entry);
836 	dom_notation_class_entry->create_object = dom_objects_new;
837 
838 	zend_hash_init(&dom_notation_prop_handlers, 0, NULL, NULL, true);
839 	DOM_REGISTER_PROP_HANDLER(&dom_notation_prop_handlers, "publicId", dom_notation_public_id_read, NULL);
840 	DOM_REGISTER_PROP_HANDLER(&dom_notation_prop_handlers, "systemId", dom_notation_system_id_read, NULL);
841 	zend_hash_merge(&dom_notation_prop_handlers, &dom_node_prop_handlers, NULL, false);
842 	zend_hash_add_new_ptr(&classes, dom_notation_class_entry->name, &dom_notation_prop_handlers);
843 
844 	dom_entity_class_entry = register_class_DOMEntity(dom_node_class_entry);
845 	dom_entity_class_entry->create_object = dom_objects_new;
846 
847 	zend_hash_init(&dom_entity_prop_handlers, 0, NULL, NULL, true);
848 	DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "publicId", dom_entity_public_id_read, NULL);
849 	DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "systemId", dom_entity_system_id_read, NULL);
850 	DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "notationName", dom_entity_notation_name_read, NULL);
851 	DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "actualEncoding", dom_entity_actual_encoding_read, NULL);
852 	DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "encoding", dom_entity_encoding_read, NULL);
853 	DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "version", dom_entity_version_read, NULL);
854 	zend_hash_merge(&dom_entity_prop_handlers, &dom_node_prop_handlers, NULL, false);
855 	zend_hash_add_new_ptr(&classes, dom_entity_class_entry->name, &dom_entity_prop_handlers);
856 
857 	dom_entityreference_class_entry = register_class_DOMEntityReference(dom_node_class_entry);
858 	dom_entityreference_class_entry->create_object = dom_objects_new;
859 	zend_hash_add_new_ptr(&classes, dom_entityreference_class_entry->name, &dom_node_prop_handlers);
860 
861 	dom_processinginstruction_class_entry = register_class_DOMProcessingInstruction(dom_node_class_entry);
862 	dom_processinginstruction_class_entry->create_object = dom_objects_new;
863 
864 	zend_hash_init(&dom_processinginstruction_prop_handlers, 0, NULL, NULL, true);
865 	DOM_REGISTER_PROP_HANDLER(&dom_processinginstruction_prop_handlers, "target", dom_processinginstruction_target_read, NULL);
866 	DOM_REGISTER_PROP_HANDLER(&dom_processinginstruction_prop_handlers, "data", dom_processinginstruction_data_read, dom_processinginstruction_data_write);
867 	zend_hash_merge(&dom_processinginstruction_prop_handlers, &dom_node_prop_handlers, NULL, false);
868 	zend_hash_add_new_ptr(&classes, dom_processinginstruction_class_entry->name, &dom_processinginstruction_prop_handlers);
869 
870 #ifdef LIBXML_XPATH_ENABLED
871 	memcpy(&dom_xpath_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
872 	dom_xpath_object_handlers.offset = XtOffsetOf(dom_xpath_object, dom) + XtOffsetOf(dom_object, std);
873 	dom_xpath_object_handlers.free_obj = dom_xpath_objects_free_storage;
874 	dom_xpath_object_handlers.get_gc = dom_xpath_get_gc;
875 
876 	dom_xpath_class_entry = register_class_DOMXPath();
877 	dom_xpath_class_entry->create_object = dom_xpath_objects_new;
878 	dom_xpath_class_entry->default_object_handlers = &dom_xpath_object_handlers;
879 
880 	zend_hash_init(&dom_xpath_prop_handlers, 0, NULL, NULL, true);
881 	DOM_REGISTER_PROP_HANDLER(&dom_xpath_prop_handlers, "document", dom_xpath_document_read, NULL);
882 	DOM_REGISTER_PROP_HANDLER(&dom_xpath_prop_handlers, "registerNodeNamespaces", dom_xpath_register_node_ns_read, dom_xpath_register_node_ns_write);
883 	zend_hash_add_new_ptr(&classes, dom_xpath_class_entry->name, &dom_xpath_prop_handlers);
884 #endif
885 
886 	register_php_dom_symbols(module_number);
887 
888 	php_libxml_register_export(dom_node_class_entry, php_dom_export_node);
889 
890 	lexbor_memory_setup(dom_malloc, dom_realloc, dom_calloc, dom_free);
891 
892 	return SUCCESS;
893 }
894 /* }}} */
895 
896 /* {{{ */
PHP_MINFO_FUNCTION(dom)897 PHP_MINFO_FUNCTION(dom)
898 {
899 	php_info_print_table_start();
900 	php_info_print_table_row(2, "DOM/XML", "enabled");
901 	php_info_print_table_row(2, "DOM/XML API Version", DOM_API_VERSION);
902 	php_info_print_table_row(2, "libxml Version", LIBXML_DOTTED_VERSION);
903 #ifdef LIBXML_HTML_ENABLED
904 	php_info_print_table_row(2, "HTML Support", "enabled");
905 #endif
906 #ifdef LIBXML_XPATH_ENABLED
907 	php_info_print_table_row(2, "XPath Support", "enabled");
908 #endif
909 #ifdef LIBXML_XPTR_ENABLED
910 	php_info_print_table_row(2, "XPointer Support", "enabled");
911 #endif
912 #ifdef LIBXML_SCHEMAS_ENABLED
913 	php_info_print_table_row(2, "Schema Support", "enabled");
914 	php_info_print_table_row(2, "RelaxNG Support", "enabled");
915 #endif
916 	php_info_print_table_end();
917 }
918 /* }}} */
919 
PHP_MSHUTDOWN_FUNCTION(dom)920 PHP_MSHUTDOWN_FUNCTION(dom) /* {{{ */
921 {
922 	zend_hash_destroy(&dom_document_prop_handlers);
923 	zend_hash_destroy(&dom_html_document_prop_handlers);
924 	zend_hash_destroy(&dom_xml_document_prop_handlers);
925 	zend_hash_destroy(&dom_documentfragment_prop_handlers);
926 	zend_hash_destroy(&dom_node_prop_handlers);
927 	zend_hash_destroy(&dom_namespace_node_prop_handlers);
928 	zend_hash_destroy(&dom_nodelist_prop_handlers);
929 	zend_hash_destroy(&dom_namednodemap_prop_handlers);
930 	zend_hash_destroy(&dom_characterdata_prop_handlers);
931 	zend_hash_destroy(&dom_attr_prop_handlers);
932 	zend_hash_destroy(&dom_element_prop_handlers);
933 	zend_hash_destroy(&dom_text_prop_handlers);
934 	zend_hash_destroy(&dom_documenttype_prop_handlers);
935 	zend_hash_destroy(&dom_notation_prop_handlers);
936 	zend_hash_destroy(&dom_entity_prop_handlers);
937 	zend_hash_destroy(&dom_processinginstruction_prop_handlers);
938 #ifdef LIBXML_XPATH_ENABLED
939 	zend_hash_destroy(&dom_xpath_prop_handlers);
940 #endif
941 	zend_hash_destroy(&classes);
942 
943 /*	If you want do find memleaks in this module, compile libxml2 with --with-mem-debug and
944 	uncomment the following line, this will tell you the amount of not freed memory
945 	and the total used memory into apaches error_log  */
946 /*  xmlMemoryDump();*/
947 
948 	return SUCCESS;
949 }
950 /* }}} */
951 
952 /* {{{ node_list_unlink */
node_list_unlink(xmlNodePtr node)953 void node_list_unlink(xmlNodePtr node)
954 {
955 	dom_object *wrapper;
956 
957 	while (node != NULL) {
958 
959 		wrapper = php_dom_object_get_data(node);
960 
961 		if (wrapper != NULL ) {
962 			xmlUnlinkNode(node);
963 		} else {
964 			if (node->type == XML_ENTITY_REF_NODE)
965 				break;
966 			node_list_unlink(node->children);
967 
968 			switch (node->type) {
969 				case XML_ATTRIBUTE_DECL:
970 				case XML_DTD_NODE:
971 				case XML_DOCUMENT_TYPE_NODE:
972 				case XML_ENTITY_DECL:
973 				case XML_ATTRIBUTE_NODE:
974 				case XML_TEXT_NODE:
975 					break;
976 				default:
977 					node_list_unlink((xmlNodePtr) node->properties);
978 			}
979 
980 		}
981 
982 		node = node->next;
983 	}
984 }
985 /* }}} end node_list_unlink */
986 
987 /* {{{ dom_objects_free_storage */
dom_objects_free_storage(zend_object * object)988 void dom_objects_free_storage(zend_object *object)
989 {
990 	dom_object *intern = php_dom_obj_from_obj(object);
991 
992 	zend_object_std_dtor(&intern->std);
993 
994 	if (intern->ptr != NULL && ((php_libxml_node_ptr *)intern->ptr)->node != NULL) {
995 		if (((xmlNodePtr) ((php_libxml_node_ptr *)intern->ptr)->node)->type != XML_DOCUMENT_NODE && ((xmlNodePtr) ((php_libxml_node_ptr *)intern->ptr)->node)->type != XML_HTML_DOCUMENT_NODE) {
996 			php_libxml_node_decrement_resource((php_libxml_node_object *) intern);
997 		} else {
998 			php_libxml_decrement_node_ptr((php_libxml_node_object *) intern);
999 			php_libxml_decrement_doc_ref((php_libxml_node_object *) intern);
1000 		}
1001 		intern->ptr = NULL;
1002 	}
1003 }
1004 /* }}} */
1005 
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)1006 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) /* {{{ */
1007 {
1008 	dom_nnodemap_object *mapptr = (dom_nnodemap_object *) intern->ptr;
1009 
1010 	ZEND_ASSERT(basenode != NULL);
1011 
1012 	ZVAL_OBJ_COPY(&mapptr->baseobj_zv, &basenode->std);
1013 
1014 	xmlDocPtr doc = basenode->document ? basenode->document->ptr : NULL;
1015 
1016 	mapptr->baseobj = basenode;
1017 	mapptr->nodetype = ntype;
1018 	mapptr->ht = ht;
1019 
1020 	const xmlChar* tmp;
1021 
1022 	if (local) {
1023 		int len = local_len > INT_MAX ? -1 : (int) local_len;
1024 		if (doc != NULL && (tmp = xmlDictExists(doc->dict, (const xmlChar *)local, len)) != NULL) {
1025 			mapptr->local = (xmlChar*) tmp;
1026 		} else {
1027 			mapptr->local = xmlCharStrndup(local, len);
1028 			mapptr->free_local = true;
1029 		}
1030 	}
1031 
1032 	if (ns) {
1033 		int len = ns_len > INT_MAX ? -1 : (int) ns_len;
1034 		if (doc != NULL && (tmp = xmlDictExists(doc->dict, (const xmlChar *)ns, len)) != NULL) {
1035 			mapptr->ns = (xmlChar*) tmp;
1036 		} else {
1037 			mapptr->ns = xmlCharStrndup(ns, len);
1038 			mapptr->free_ns = true;
1039 		}
1040 	}
1041 }
1042 /* }}} */
1043 
dom_objects_set_class_ex(zend_class_entry * class_type,dom_object * intern)1044 static void dom_objects_set_class_ex(zend_class_entry *class_type, dom_object *intern)
1045 {
1046 	zend_class_entry *base_class = class_type;
1047 	while ((base_class->type != ZEND_INTERNAL_CLASS || base_class->info.internal.module->module_number != dom_module_entry.module_number) && base_class->parent != NULL) {
1048 		base_class = base_class->parent;
1049 	}
1050 
1051 	intern->prop_handler = zend_hash_find_ptr(&classes, base_class->name);
1052 
1053 	zend_object_std_init(&intern->std, class_type);
1054 	object_properties_init(&intern->std, class_type);
1055 }
1056 
dom_objects_set_class(zend_class_entry * class_type)1057 static dom_object* dom_objects_set_class(zend_class_entry *class_type)
1058 {
1059 	dom_object *intern = zend_object_alloc(sizeof(dom_object), class_type);
1060 	dom_objects_set_class_ex(class_type, intern);
1061 	return intern;
1062 }
1063 
1064 /* {{{ dom_objects_new */
dom_objects_new(zend_class_entry * class_type)1065 zend_object *dom_objects_new(zend_class_entry *class_type)
1066 {
1067 	dom_object *intern = dom_objects_set_class(class_type);
1068 	intern->std.handlers = &dom_object_handlers;
1069 	return &intern->std;
1070 }
1071 /* }}} */
1072 
dom_objects_namespace_node_new(zend_class_entry * class_type)1073 static zend_object *dom_objects_namespace_node_new(zend_class_entry *class_type)
1074 {
1075 	dom_object_namespace_node *intern = zend_object_alloc(sizeof(dom_object_namespace_node), class_type);
1076 	dom_objects_set_class_ex(class_type, &intern->dom);
1077 	intern->dom.std.handlers = &dom_object_namespace_node_handlers;
1078 	return &intern->dom.std;
1079 }
1080 
dom_object_namespace_node_free_storage(zend_object * object)1081 static void dom_object_namespace_node_free_storage(zend_object *object)
1082 {
1083 	dom_object_namespace_node *intern = php_dom_namespace_node_obj_from_obj(object);
1084 	if (intern->parent_intern != NULL) {
1085 		zval tmp;
1086 		ZVAL_OBJ(&tmp, &intern->parent_intern->std);
1087 		zval_ptr_dtor(&tmp);
1088 	}
1089 	dom_objects_free_storage(object);
1090 }
1091 
1092 #ifdef LIBXML_XPATH_ENABLED
1093 
1094 /* {{{ zend_object dom_xpath_objects_new(zend_class_entry *class_type) */
dom_xpath_objects_new(zend_class_entry * class_type)1095 zend_object *dom_xpath_objects_new(zend_class_entry *class_type)
1096 {
1097 	dom_xpath_object *intern = zend_object_alloc(sizeof(dom_xpath_object), class_type);
1098 
1099 	php_dom_xpath_callbacks_ctor(&intern->xpath_callbacks);
1100 	intern->register_node_ns = 1;
1101 
1102 	intern->dom.prop_handler = &dom_xpath_prop_handlers;
1103 
1104 	zend_object_std_init(&intern->dom.std, class_type);
1105 	object_properties_init(&intern->dom.std, class_type);
1106 
1107 	return &intern->dom.std;
1108 }
1109 /* }}} */
1110 
1111 #endif
1112 
dom_nnodemap_objects_free_storage(zend_object * object)1113 void dom_nnodemap_objects_free_storage(zend_object *object) /* {{{ */
1114 {
1115 	dom_object *intern = php_dom_obj_from_obj(object);
1116 	dom_nnodemap_object *objmap = (dom_nnodemap_object *)intern->ptr;
1117 
1118 	if (objmap) {
1119 		if (objmap->cached_obj && GC_DELREF(&objmap->cached_obj->std) == 0) {
1120 			zend_objects_store_del(&objmap->cached_obj->std);
1121 		}
1122 		if (objmap->free_local) {
1123 			xmlFree(objmap->local);
1124 		}
1125 		if (objmap->free_ns) {
1126 			xmlFree(objmap->ns);
1127 		}
1128 		if (!Z_ISUNDEF(objmap->baseobj_zv)) {
1129 			zval_ptr_dtor(&objmap->baseobj_zv);
1130 		}
1131 		efree(objmap);
1132 		intern->ptr = NULL;
1133 	}
1134 
1135 	php_libxml_decrement_doc_ref((php_libxml_node_object *)intern);
1136 
1137 	zend_object_std_dtor(&intern->std);
1138 }
1139 /* }}} */
1140 
dom_nnodemap_objects_new(zend_class_entry * class_type)1141 zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type)
1142 {
1143 	dom_object *intern;
1144 	dom_nnodemap_object *objmap;
1145 
1146 	intern = dom_objects_set_class(class_type);
1147 	intern->ptr = emalloc(sizeof(dom_nnodemap_object));
1148 	objmap = (dom_nnodemap_object *)intern->ptr;
1149 	ZVAL_UNDEF(&objmap->baseobj_zv);
1150 	objmap->baseobj = NULL;
1151 	objmap->nodetype = 0;
1152 	objmap->ht = NULL;
1153 	objmap->local = NULL;
1154 	objmap->free_local = false;
1155 	objmap->ns = NULL;
1156 	objmap->free_ns = false;
1157 	objmap->cache_tag.modification_nr = 0;
1158 	objmap->cached_length = -1;
1159 	objmap->cached_obj = NULL;
1160 	objmap->cached_obj_index = 0;
1161 
1162 	return &intern->std;
1163 }
1164 
php_dom_create_iterator(zval * return_value,int ce_type)1165 void php_dom_create_iterator(zval *return_value, int ce_type) /* {{{ */
1166 {
1167 	zend_class_entry *ce;
1168 
1169 	if (ce_type == DOM_NAMEDNODEMAP) {
1170 		ce = dom_namednodemap_class_entry;
1171 	} else {
1172 		ce = dom_nodelist_class_entry;
1173 	}
1174 
1175 	object_init_ex(return_value, ce);
1176 }
1177 /* }}} */
1178 
1179 /* {{{ php_dom_create_object */
php_dom_create_object(xmlNodePtr obj,zval * return_value,dom_object * domobj)1180 PHP_DOM_EXPORT bool php_dom_create_object(xmlNodePtr obj, zval *return_value, dom_object *domobj)
1181 {
1182 	zend_class_entry *ce;
1183 	dom_object *intern;
1184 
1185 	if (!obj) {
1186 		ZVAL_NULL(return_value);
1187 		return 0;
1188 	}
1189 
1190 	if ((intern = (dom_object *) php_dom_object_get_data((void *) obj))) {
1191 		ZVAL_OBJ_COPY(return_value, &intern->std);
1192 		return 1;
1193 	}
1194 
1195 	switch (obj->type) {
1196 		case XML_DOCUMENT_NODE:
1197 		{
1198 			if (domobj && domobj->document->is_modern_api_class) {
1199 				ce = dom_xml_document_class_entry;
1200 			} else {
1201 				ce = dom_document_class_entry;
1202 			}
1203 			break;
1204 		}
1205 		case XML_HTML_DOCUMENT_NODE:
1206 		{
1207 			if (domobj && domobj->document->is_modern_api_class) {
1208 				ce = dom_html_document_class_entry;
1209 			} else {
1210 				ce = dom_document_class_entry;
1211 			}
1212 			break;
1213 		}
1214 		case XML_DTD_NODE:
1215 		case XML_DOCUMENT_TYPE_NODE:
1216 		{
1217 			ce = dom_documenttype_class_entry;
1218 			break;
1219 		}
1220 		case XML_ELEMENT_NODE:
1221 		{
1222 			ce = dom_element_class_entry;
1223 			break;
1224 		}
1225 		case XML_ATTRIBUTE_NODE:
1226 		{
1227 			ce = dom_attr_class_entry;
1228 			break;
1229 		}
1230 		case XML_TEXT_NODE:
1231 		{
1232 			ce = dom_text_class_entry;
1233 			break;
1234 		}
1235 		case XML_COMMENT_NODE:
1236 		{
1237 			ce = dom_comment_class_entry;
1238 			break;
1239 		}
1240 		case XML_PI_NODE:
1241 		{
1242 			ce = dom_processinginstruction_class_entry;
1243 			break;
1244 		}
1245 		case XML_ENTITY_REF_NODE:
1246 		{
1247 			ce = dom_entityreference_class_entry;
1248 			break;
1249 		}
1250 		case XML_ENTITY_DECL:
1251 		case XML_ELEMENT_DECL:
1252 		{
1253 			ce = dom_entity_class_entry;
1254 			break;
1255 		}
1256 		case XML_CDATA_SECTION_NODE:
1257 		{
1258 			ce = dom_cdatasection_class_entry;
1259 			break;
1260 		}
1261 		case XML_DOCUMENT_FRAG_NODE:
1262 		{
1263 			ce = dom_documentfragment_class_entry;
1264 			break;
1265 		}
1266 		case XML_NOTATION_NODE:
1267 		{
1268 			ce = dom_notation_class_entry;
1269 			break;
1270 		}
1271 		case XML_NAMESPACE_DECL:
1272 		{
1273 			ce = dom_namespace_node_class_entry;
1274 			break;
1275 		}
1276 		default:
1277 			/* TODO Convert to a ZEND assertion? */
1278 			zend_throw_error(NULL, "Unsupported node type: %d", obj->type);
1279 			ZVAL_NULL(return_value);
1280 			return 0;
1281 	}
1282 
1283 	if (domobj && domobj->document) {
1284 		ce = dom_get_doc_classmap(domobj->document, ce);
1285 	}
1286 	php_dom_instantiate_object_helper(return_value, ce, obj, domobj);
1287 	return 0;
1288 }
1289 /* }}} end php_domobject_new */
1290 
php_dom_instantiate_object_helper(zval * return_value,zend_class_entry * ce,xmlNodePtr obj,dom_object * parent)1291 dom_object *php_dom_instantiate_object_helper(zval *return_value, zend_class_entry *ce, xmlNodePtr obj, dom_object *parent)
1292 {
1293 	object_init_ex(return_value, ce);
1294 
1295 	dom_object *intern = Z_DOMOBJ_P(return_value);
1296 	if (obj->doc != NULL) {
1297 		if (parent != NULL) {
1298 			intern->document = parent->document;
1299 		}
1300 		php_libxml_increment_doc_ref((php_libxml_node_object *)intern, obj->doc);
1301 	}
1302 
1303 	php_libxml_increment_node_ptr((php_libxml_node_object *)intern, obj, (void *)intern);
1304 
1305 	return intern;
1306 }
1307 
php_dom_create_implementation(zval * retval)1308 void php_dom_create_implementation(zval *retval) {
1309 	object_init_ex(retval, dom_domimplementation_class_entry);
1310 }
1311 
1312 /* {{{ int dom_hierarchy(xmlNodePtr parent, xmlNodePtr child) */
dom_hierarchy(xmlNodePtr parent,xmlNodePtr child)1313 int dom_hierarchy(xmlNodePtr parent, xmlNodePtr child)
1314 {
1315 	xmlNodePtr nodep;
1316 
1317 	if (parent == NULL || child == NULL || child->doc != parent->doc) {
1318 		return SUCCESS;
1319 	}
1320 
1321 	if (child->type == XML_DOCUMENT_NODE) {
1322 		return FAILURE;
1323 	}
1324 
1325 	nodep = parent;
1326 
1327 	while (nodep) {
1328 		if (nodep == child) {
1329 			return FAILURE;
1330 		}
1331 		nodep = nodep->parent;
1332 	}
1333 
1334 	return SUCCESS;
1335 }
1336 /* }}} end dom_hierarchy */
1337 
1338 /* {{{ */
dom_has_feature(zend_string * feature,zend_string * version)1339 bool dom_has_feature(zend_string *feature, zend_string *version)
1340 {
1341 	if (zend_string_equals_literal(version, "1.0")
1342 		|| zend_string_equals_literal(version, "2.0")
1343 		|| zend_string_equals_literal(version, "")
1344 	) {
1345 		if (zend_string_equals_literal_ci(feature, "XML")
1346 			|| (zend_string_equals_literal_ci(feature, "Core") && zend_string_equals_literal(version, "1.0"))
1347 		) {
1348 			return true;
1349 		}
1350 	}
1351 
1352 	return false;
1353 }
1354 /* }}} end dom_has_feature */
1355 
dom_get_elements_by_tag_name_ns_raw(xmlNodePtr basep,xmlNodePtr nodep,char * ns,char * local,int * cur,int index)1356 xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr basep, xmlNodePtr nodep, char *ns, char *local, int *cur, int index) /* {{{ */
1357 {
1358 	/* Can happen with detached document */
1359 	if (UNEXPECTED(nodep == NULL)) {
1360 		return NULL;
1361 	}
1362 
1363 	xmlNodePtr ret = NULL;
1364 	bool local_match_any = local[0] == '*' && local[1] == '\0';
1365 
1366 	/* Note: The spec says that ns == '' must be transformed to ns == NULL. In other words, they are equivalent.
1367 	 *       PHP however does not do this and internally uses the empty string everywhere when the user provides ns == NULL.
1368 	 *       This is because for PHP ns == NULL has another meaning: "match every namespace" instead of "match the empty namespace". */
1369 	bool ns_match_any = ns == NULL || (ns[0] == '*' && ns[1] == '\0');
1370 
1371 	while (*cur <= index) {
1372 		if (nodep->type == XML_ELEMENT_NODE) {
1373 			if (local_match_any || xmlStrEqual(nodep->name, (xmlChar *)local)) {
1374 				if (ns_match_any || (ns[0] == '\0' && nodep->ns == NULL) || (nodep->ns != NULL && xmlStrEqual(nodep->ns->href, (xmlChar *)ns))) {
1375 					if (*cur == index) {
1376 						ret = nodep;
1377 						break;
1378 					}
1379 					(*cur)++;
1380 				}
1381 			}
1382 
1383 			if (nodep->children) {
1384 				nodep = nodep->children;
1385 				continue;
1386 			}
1387 		}
1388 
1389 		if (nodep->next) {
1390 			nodep = nodep->next;
1391 		} else {
1392 			/* Go upwards, until we find a parent node with a next sibling, or until we hit the base. */
1393 			do {
1394 				nodep = nodep->parent;
1395 				if (nodep == basep) {
1396 					return NULL;
1397 				}
1398 				/* This shouldn't happen, unless there's an invalidation bug somewhere. */
1399 				if (UNEXPECTED(nodep == NULL)) {
1400 					zend_throw_error(NULL, "Current node in traversal is not in the document. Please report this as a bug in php-src.");
1401 					return NULL;
1402 				}
1403 			} while (nodep->next == NULL);
1404 			nodep = nodep->next;
1405 		}
1406 	}
1407 	return ret;
1408 }
1409 /* }}} end dom_element_get_elements_by_tag_name_ns_raw */
1410 
is_empty_node(xmlNodePtr nodep)1411 static inline bool is_empty_node(xmlNodePtr nodep)
1412 {
1413 	xmlChar	*strContent = xmlNodeGetContent(nodep);
1414 	bool ret = strContent == NULL || *strContent == '\0';
1415 	xmlFree(strContent);
1416 	return ret;
1417 }
1418 
1419 /* {{{ void dom_normalize (xmlNodePtr nodep) */
dom_normalize(xmlNodePtr nodep)1420 void dom_normalize (xmlNodePtr nodep)
1421 {
1422 	xmlNodePtr child, nextp, newnextp;
1423 	xmlAttrPtr attr;
1424 	xmlChar	*strContent;
1425 
1426 	child = nodep->children;
1427 	while(child != NULL) {
1428 		switch (child->type) {
1429 			case XML_TEXT_NODE:
1430 				nextp = child->next;
1431 				while (nextp != NULL) {
1432 					if (nextp->type == XML_TEXT_NODE) {
1433 						newnextp = nextp->next;
1434 						strContent = xmlNodeGetContent(nextp);
1435 						xmlNodeAddContent(child, strContent);
1436 						xmlFree(strContent);
1437 						xmlUnlinkNode(nextp);
1438 						php_libxml_node_free_resource(nextp);
1439 						nextp = newnextp;
1440 					} else {
1441 						break;
1442 					}
1443 				}
1444 				if (is_empty_node(child)) {
1445 					nextp = child->next;
1446 					xmlUnlinkNode(child);
1447 					php_libxml_node_free_resource(child);
1448 					child = nextp;
1449 					continue;
1450 				}
1451 				break;
1452 			case XML_ELEMENT_NODE:
1453 				dom_normalize (child);
1454 				attr = child->properties;
1455 				while (attr != NULL) {
1456 					dom_normalize((xmlNodePtr) attr);
1457 					attr = attr->next;
1458 				}
1459 				break;
1460 			case XML_ATTRIBUTE_NODE:
1461 				dom_normalize (child);
1462 				break;
1463 			default:
1464 				break;
1465 		}
1466 		child = child->next;
1467 	}
1468 }
1469 /* }}} end dom_normalize */
1470 
dom_reconcile_ns_internal(xmlDocPtr doc,xmlNodePtr nodep,xmlNodePtr search_parent)1471 static void dom_reconcile_ns_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr search_parent)
1472 {
1473 	xmlNsPtr nsptr, nsdftptr, curns, prevns = NULL;
1474 
1475 	/* Following if block primarily used for inserting nodes created via createElementNS */
1476 	if (nodep->nsDef != NULL) {
1477 		curns = nodep->nsDef;
1478 		while (curns) {
1479 			nsdftptr = curns->next;
1480 			if (curns->href != NULL) {
1481 				if((nsptr = xmlSearchNsByHref(doc, search_parent, curns->href)) &&
1482 					(curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) {
1483 					curns->next = NULL;
1484 					if (prevns == NULL) {
1485 						nodep->nsDef = nsdftptr;
1486 					} else {
1487 						prevns->next = nsdftptr;
1488 					}
1489 					/* Note: we can't get here if the ns is already on the oldNs list.
1490 					 * This is because in that case the definition won't be on the node, and
1491 					 * therefore won't be in the nodep->nsDef list. */
1492 					php_libxml_set_old_ns(doc, curns);
1493 					curns = prevns;
1494 				}
1495 			}
1496 			prevns = curns;
1497 			curns = nsdftptr;
1498 		}
1499 	}
1500 }
1501 
php_dom_reconcile_attribute_namespace_after_insertion(xmlAttrPtr attrp)1502 void php_dom_reconcile_attribute_namespace_after_insertion(xmlAttrPtr attrp)
1503 {
1504 	ZEND_ASSERT(attrp != NULL);
1505 
1506 	if (attrp->ns != NULL) {
1507 		/* Try to link to an existing namespace. If that won't work, reconcile. */
1508 		xmlNodePtr nodep = attrp->parent;
1509 		xmlNsPtr matching_ns = xmlSearchNs(nodep->doc, nodep, attrp->ns->prefix);
1510 		if (matching_ns && xmlStrEqual(matching_ns->href, attrp->ns->href)) {
1511 			attrp->ns = matching_ns;
1512 		} else {
1513 			if (attrp->ns->prefix != NULL) {
1514 				xmlReconciliateNs(nodep->doc, nodep);
1515 			}
1516 		}
1517 	}
1518 }
1519 
dom_libxml_reconcile_ensure_namespaces_are_declared(xmlNodePtr nodep)1520 static void dom_libxml_reconcile_ensure_namespaces_are_declared(xmlNodePtr nodep)
1521 {
1522 	/* Ideally we'd use the DOM-wrapped version, but we cannot: https://github.com/php/php-src/pull/12308. */
1523 #if 0
1524 	/* Put on stack to avoid allocation.
1525 	 * Although libxml2 currently does not use this for the reconciliation, it still
1526 	 * makes sense to do this just in case libxml2's internal change in the future. */
1527 	xmlDOMWrapCtxt dummy_ctxt = {0};
1528 	xmlDOMWrapReconcileNamespaces(&dummy_ctxt, nodep, /* options */ 0);
1529 #else
1530 	xmlReconciliateNs(nodep->doc, nodep);
1531 #endif
1532 }
1533 
dom_reconcile_ns(xmlDocPtr doc,xmlNodePtr nodep)1534 void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */
1535 {
1536 	ZEND_ASSERT(nodep->type != XML_ATTRIBUTE_NODE);
1537 
1538 	/* Although the node type will be checked by the libxml2 API,
1539 	 * we still want to do the internal reconciliation conditionally. */
1540 	if (nodep->type == XML_ELEMENT_NODE) {
1541 		dom_reconcile_ns_internal(doc, nodep, nodep->parent);
1542 		dom_libxml_reconcile_ensure_namespaces_are_declared(nodep);
1543 	}
1544 }
1545 /* }}} */
1546 
dom_reconcile_ns_list_internal(xmlDocPtr doc,xmlNodePtr nodep,xmlNodePtr last,xmlNodePtr search_parent)1547 static void dom_reconcile_ns_list_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last, xmlNodePtr search_parent)
1548 {
1549 	ZEND_ASSERT(nodep != NULL);
1550 	while (true) {
1551 		if (nodep->type == XML_ELEMENT_NODE) {
1552 			dom_reconcile_ns_internal(doc, nodep, search_parent);
1553 			if (nodep->children) {
1554 				dom_reconcile_ns_list_internal(doc, nodep->children, nodep->last /* process the whole children list */, search_parent);
1555 			}
1556 		}
1557 		if (nodep == last) {
1558 			break;
1559 		}
1560 		nodep = nodep->next;
1561 	}
1562 }
1563 
dom_reconcile_ns_list(xmlDocPtr doc,xmlNodePtr nodep,xmlNodePtr last)1564 void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last)
1565 {
1566 	dom_reconcile_ns_list_internal(doc, nodep, last, nodep->parent);
1567 	/* The loop is outside of the recursion in the above call because
1568 	 * dom_libxml_reconcile_ensure_namespaces_are_declared() performs its own recursion. */
1569 	while (true) {
1570 		/* The internal libxml2 call will already check the node type, no need for us to do it here. */
1571 		dom_libxml_reconcile_ensure_namespaces_are_declared(nodep);
1572 		if (nodep == last) {
1573 			break;
1574 		}
1575 		nodep = nodep->next;
1576 	}
1577 }
1578 
1579 /*
1580 http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS
1581 
1582 NAMESPACE_ERR: Raised if
1583 
1584 1. the qualifiedName is a malformed qualified name
1585 2. the qualifiedName has a prefix and the  namespaceURI is null
1586 */
1587 
1588 /* {{{ 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)1589 int dom_check_qname(char *qname, char **localname, char **prefix, int uri_len, int name_len) {
1590 	if (name_len == 0) {
1591 		return NAMESPACE_ERR;
1592 	}
1593 
1594 	*localname = (char *)xmlSplitQName2((xmlChar *)qname, (xmlChar **) prefix);
1595 	if (*localname == NULL) {
1596 		*localname = (char *)xmlStrdup((xmlChar *)qname);
1597 		if (*prefix == NULL && uri_len == 0) {
1598 			return 0;
1599 		}
1600 	}
1601 
1602 	/* 1 */
1603 	if (xmlValidateQName((xmlChar *) qname, 0) != 0) {
1604 		return NAMESPACE_ERR;
1605 	}
1606 
1607 	/* 2 */
1608 	if (*prefix != NULL && uri_len == 0) {
1609 		return NAMESPACE_ERR;
1610 	}
1611 
1612 	return 0;
1613 }
1614 /* }}} */
1615 
1616 /* Creates a new namespace declaration with a random prefix with the given uri on the tree.
1617  * This is used to resolve a namespace prefix conflict in cases where spec does not want a
1618  * namespace error in case of conflicts, but demands a resolution. */
dom_get_ns_resolve_prefix_conflict(xmlNodePtr tree,const char * uri)1619 xmlNsPtr dom_get_ns_resolve_prefix_conflict(xmlNodePtr tree, const char *uri)
1620 {
1621 	ZEND_ASSERT(tree != NULL);
1622 	xmlDocPtr doc = tree->doc;
1623 
1624 	if (UNEXPECTED(doc == NULL)) {
1625 		return NULL;
1626 	}
1627 
1628 	/* Code adapted from libxml2 (2.10.4) */
1629 	char prefix[50];
1630 	int counter = 1;
1631 	snprintf(prefix, sizeof(prefix), "default");
1632 	xmlNsPtr nsptr = xmlSearchNs(doc, tree, (const xmlChar *) prefix);
1633 	while (nsptr != NULL) {
1634 		if (counter > 1000) {
1635 			return NULL;
1636 		}
1637 		snprintf(prefix, sizeof(prefix), "default%d", counter++);
1638 		nsptr = xmlSearchNs(doc, tree, (const xmlChar *) prefix);
1639 	}
1640 
1641 	/* Search yielded no conflict */
1642 	return xmlNewNs(tree, (const xmlChar *) uri, (const xmlChar *) prefix);
1643 }
1644 
1645 /*
1646 http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS
1647 
1648 NAMESPACE_ERR: Raised if
1649 
1650 3. the qualifiedName has a prefix that is "xml" and the namespaceURI is different from "http://www.w3.org/XML/1998/namespace" [XML Namespaces]
1651 4. the qualifiedName or its prefix is "xmlns" and the namespaceURI is different from  "http://www.w3.org/2000/xmlns/"
1652 5. the namespaceURI is "http://www.w3.org/2000/xmlns/" and neither the	qualifiedName nor its prefix is "xmlns".
1653 */
1654 
dom_get_ns_unchecked(xmlNodePtr nodep,char * uri,char * prefix)1655 xmlNsPtr dom_get_ns_unchecked(xmlNodePtr nodep, char *uri, char *prefix)
1656 {
1657 	xmlNsPtr nsptr = xmlNewNs(nodep, (xmlChar *)uri, (xmlChar *)prefix);
1658 	if (UNEXPECTED(nsptr == NULL)) {
1659 		/* Either memory allocation failure, or it's because of a prefix conflict.
1660 		 * We'll assume a conflict and try again. If it was a memory allocation failure we'll just fail again, whatever.
1661 		 * 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?".
1662 		 * This branch will also be taken unlikely anyway as in those cases it'll be for allocation failure. */
1663 		return dom_get_ns_resolve_prefix_conflict(nodep, uri);
1664 	}
1665 
1666 	return nsptr;
1667 }
1668 
1669 /* {{{ xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) */
dom_get_ns(xmlNodePtr nodep,char * uri,int * errorcode,char * prefix)1670 xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) {
1671 	xmlNsPtr nsptr;
1672 
1673 	if (! ((prefix && !strcmp (prefix, "xml") && strcmp(uri, (char *)XML_XML_NAMESPACE)) ||
1674 		   (prefix && !strcmp (prefix, "xmlns") && strcmp(uri, (char *)DOM_XMLNS_NAMESPACE)) ||
1675 		   (prefix && !strcmp(uri, (char *)DOM_XMLNS_NAMESPACE) && strcmp (prefix, "xmlns")))) {
1676 		nsptr = dom_get_ns_unchecked(nodep, uri, prefix);
1677 		if (UNEXPECTED(nsptr == NULL)) {
1678 			goto err;
1679 		}
1680 	} else {
1681 		goto err;
1682 	}
1683 
1684 	*errorcode = 0;
1685 	return nsptr;
1686 err:
1687 	*errorcode = NAMESPACE_ERR;
1688 	return NULL;
1689 }
1690 /* }}} end dom_get_ns */
1691 
1692 /* {{{ xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName) */
dom_get_nsdecl(xmlNode * node,xmlChar * localName)1693 xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName) {
1694 	xmlNsPtr cur;
1695 	xmlNs *ret = NULL;
1696 	if (node == NULL)
1697 		return NULL;
1698 
1699 	if (localName == NULL || localName[0] == '\0') {
1700 		cur = node->nsDef;
1701 		while (cur != NULL) {
1702 			if (cur->prefix == NULL  && cur->href != NULL) {
1703 				ret = cur;
1704 				break;
1705 			}
1706 			cur = cur->next;
1707 		}
1708 	} else {
1709 		cur = node->nsDef;
1710 		while (cur != NULL) {
1711 			if (cur->prefix != NULL && xmlStrEqual(localName, cur->prefix)) {
1712 				ret = cur;
1713 				break;
1714 			}
1715 			cur = cur->next;
1716 		}
1717 	}
1718 	return ret;
1719 }
1720 /* }}} end dom_get_nsdecl */
1721 
php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep,xmlNsPtr original)1722 static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original)
1723 {
1724 	xmlNodePtr attrp;
1725 	xmlNsPtr curns = xmlNewNs(NULL, original->href, NULL);
1726 	if (original->prefix) {
1727 		curns->prefix = xmlStrdup(original->prefix);
1728 		attrp = xmlNewDocNode(nodep->doc, NULL, (xmlChar *) original->prefix, original->href);
1729 	} else {
1730 		attrp = xmlNewDocNode(nodep->doc, NULL, (xmlChar *)"xmlns", original->href);
1731 	}
1732 	attrp->type = XML_NAMESPACE_DECL;
1733 	attrp->parent = nodep;
1734 	attrp->ns = curns;
1735 	return attrp;
1736 }
1737 
1738 /* 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)1739 xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern)
1740 {
1741 	xmlNodePtr attrp = php_dom_create_fake_namespace_decl_node_ptr(nodep, original);
1742 	php_dom_create_object(attrp, return_value, parent_intern);
1743 	/* This object must exist, because we just created an object for it via php_dom_create_object(). */
1744 	php_dom_namespace_node_obj_from_obj(Z_OBJ_P(return_value))->parent_intern = parent_intern;
1745 	return attrp;
1746 }
1747 
dom_nodemap_or_nodelist_process_offset_as_named(zval * offset,zend_long * lval)1748 static bool dom_nodemap_or_nodelist_process_offset_as_named(zval *offset, zend_long *lval)
1749 {
1750 	if (Z_TYPE_P(offset) == IS_STRING) {
1751 		/* See zval_get_long_func() */
1752 		double dval;
1753 		zend_uchar is_numeric_string_type;
1754 		if (0 == (is_numeric_string_type = is_numeric_string(Z_STRVAL_P(offset), Z_STRLEN_P(offset), lval, &dval, true))) {
1755 			return true;
1756 		} else if (is_numeric_string_type == IS_DOUBLE) {
1757 			*lval = zend_dval_to_lval_cap(dval);
1758 		}
1759 	} else {
1760 		*lval = zval_get_long(offset);
1761 	}
1762 	return false;
1763 }
1764 
dom_nodelist_read_dimension(zend_object * object,zval * offset,int type,zval * rv)1765 static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv) /* {{{ */
1766 {
1767 	if (UNEXPECTED(!offset)) {
1768 		zend_throw_error(NULL, "Cannot access DOMNodeList without offset");
1769 		return NULL;
1770 	}
1771 
1772 	zend_long lval;
1773 	if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) {
1774 		/* does not support named lookup */
1775 		ZVAL_NULL(rv);
1776 		return rv;
1777 	}
1778 
1779 	php_dom_nodelist_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, lval, rv);
1780 	return rv;
1781 } /* }}} end dom_nodelist_read_dimension */
1782 
dom_nodelist_has_dimension(zend_object * object,zval * member,int check_empty)1783 static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty)
1784 {
1785 	zend_long offset;
1786 	if (dom_nodemap_or_nodelist_process_offset_as_named(member, &offset)) {
1787 		/* does not support named lookup */
1788 		return 0;
1789 	}
1790 
1791 	return offset >= 0 && offset < php_dom_get_nodelist_length(php_dom_obj_from_obj(object));
1792 } /* }}} end dom_nodelist_has_dimension */
1793 
dom_remove_all_children(xmlNodePtr nodep)1794 void dom_remove_all_children(xmlNodePtr nodep)
1795 {
1796 	if (nodep->children) {
1797 		node_list_unlink(nodep->children);
1798 		php_libxml_node_free_list((xmlNodePtr) nodep->children);
1799 		nodep->children = NULL;
1800 		nodep->last = NULL;
1801 	}
1802 }
1803 
php_dom_get_content_into_zval(const xmlNode * nodep,zval * retval,bool default_is_null)1804 void php_dom_get_content_into_zval(const xmlNode *nodep, zval *retval, bool default_is_null)
1805 {
1806 	ZEND_ASSERT(nodep != NULL);
1807 
1808 	if (nodep->type == XML_TEXT_NODE
1809 		|| nodep->type == XML_CDATA_SECTION_NODE
1810 		|| nodep->type == XML_PI_NODE
1811 		|| nodep->type == XML_COMMENT_NODE) {
1812 		char *str = (char * ) nodep->content;
1813 		if (str != NULL) {
1814 			ZVAL_STRING(retval, str);
1815 		} else {
1816 			goto failure;
1817 		}
1818 		return;
1819 	}
1820 
1821 	char *str = (char *) xmlNodeGetContent(nodep);
1822 
1823 	if (str != NULL) {
1824 		ZVAL_STRING(retval, str);
1825 		xmlFree(str);
1826 		return;
1827 	}
1828 
1829 failure:
1830 	if (default_is_null) {
1831 		ZVAL_NULL(retval);
1832 	} else {
1833 		ZVAL_EMPTY_STRING(retval);
1834 	}
1835 }
1836 
dom_nodemap_read_dimension(zend_object * object,zval * offset,int type,zval * rv)1837 static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv) /* {{{ */
1838 {
1839 	if (UNEXPECTED(!offset)) {
1840 		zend_throw_error(NULL, "Cannot access DOMNamedNodeMap without offset");
1841 		return NULL;
1842 	}
1843 
1844 	zend_long lval;
1845 	if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) {
1846 		/* exceptional case, switch to named lookup */
1847 		php_dom_named_node_map_get_named_item_into_zval(php_dom_obj_from_obj(object)->ptr, Z_STRVAL_P(offset), rv);
1848 		return rv;
1849 	}
1850 
1851 	/* see PHP_METHOD(DOMNamedNodeMap, item) */
1852 	if (UNEXPECTED(lval < 0 || ZEND_LONG_INT_OVFL(lval))) {
1853 		zend_value_error("must be between 0 and %d", INT_MAX);
1854 		return NULL;
1855 	}
1856 
1857 	php_dom_named_node_map_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, lval, rv);
1858 	return rv;
1859 } /* }}} end dom_nodemap_read_dimension */
1860 
dom_nodemap_has_dimension(zend_object * object,zval * member,int check_empty)1861 static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty)
1862 {
1863 	zend_long offset;
1864 	if (dom_nodemap_or_nodelist_process_offset_as_named(member, &offset)) {
1865 		/* exceptional case, switch to named lookup */
1866 		return php_dom_named_node_map_get_named_item(php_dom_obj_from_obj(object)->ptr, Z_STRVAL_P(member), false) != NULL;
1867 	}
1868 
1869 	return offset >= 0 && offset < php_dom_get_namednodemap_length(php_dom_obj_from_obj(object));
1870 } /* }}} end dom_nodemap_has_dimension */
1871 
dom_clone_node(xmlNodePtr node,xmlDocPtr doc,const dom_object * intern,bool recursive)1872 xmlNodePtr dom_clone_node(xmlNodePtr node, xmlDocPtr doc, const dom_object *intern, bool recursive)
1873 {
1874 	/* See http://www.xmlsoft.org/html/libxml-tree.html#xmlDocCopyNode for meaning of values */
1875 	int extended_recursive = recursive;
1876 	if (!recursive && node->type == XML_ELEMENT_NODE) {
1877 		extended_recursive = 2;
1878 	}
1879 	xmlNodePtr copy = xmlDocCopyNode(node, doc, extended_recursive);
1880 	if (UNEXPECTED(!copy)) {
1881 		return NULL;
1882 	}
1883 
1884 	if (intern->document && intern->document->is_modern_api_class) {
1885 		dom_mark_namespaces_for_copy_based_on_copy(copy, node);
1886 	}
1887 
1888 	return copy;
1889 }
1890 
1891 #endif /* HAVE_DOM */
1892