xref: /PHP-8.4/ext/dom/private_data.c (revision 6980eba8)
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: Niels Dossche <nielsdos@php.net>                            |
14    +----------------------------------------------------------------------+
15 */
16 
17 #ifdef HAVE_CONFIG_H
18 #include <config.h>
19 #endif
20 
21 #include "php.h"
22 #if defined(HAVE_LIBXML) && defined(HAVE_DOM)
23 #include "php_dom.h"
24 #include "private_data.h"
25 #include "internal_helpers.h"
26 
php_dom_libxml_private_data_destroy(php_libxml_private_data_header * header)27 static void php_dom_libxml_private_data_destroy(php_libxml_private_data_header *header)
28 {
29 	php_dom_private_data_destroy((php_dom_private_data *) header);
30 }
31 
php_dom_libxml_private_data_ns_hook(php_libxml_private_data_header * header,xmlNodePtr node)32 static void php_dom_libxml_private_data_ns_hook(php_libxml_private_data_header *header, xmlNodePtr node)
33 {
34 	php_dom_remove_templated_content((php_dom_private_data *) header, node);
35 }
36 
php_dom_libxml_private_data_header(php_dom_private_data * private_data)37 php_libxml_private_data_header *php_dom_libxml_private_data_header(php_dom_private_data *private_data)
38 {
39 	return private_data == NULL ? NULL : &private_data->header;
40 }
41 
php_dom_ns_mapper_from_private(php_dom_private_data * private_data)42 php_dom_libxml_ns_mapper *php_dom_ns_mapper_from_private(php_dom_private_data *private_data)
43 {
44 	return private_data == NULL ? NULL : &private_data->ns_mapper;
45 }
46 
php_dom_private_data_create(void)47 php_dom_private_data *php_dom_private_data_create(void)
48 {
49 	php_dom_private_data *private_data = emalloc(sizeof(*private_data));
50 	private_data->header.dtor = php_dom_libxml_private_data_destroy;
51 	private_data->header.ns_hook = php_dom_libxml_private_data_ns_hook;
52 	private_data->ns_mapper.html_ns = NULL;
53 	private_data->ns_mapper.prefixless_xmlns_ns = NULL;
54 	zend_hash_init(&private_data->ns_mapper.uri_to_prefix_map, 0, NULL, ZVAL_PTR_DTOR, false);
55 	private_data->template_fragments = NULL;
56 	return private_data;
57 }
58 
php_dom_private_data_destroy(php_dom_private_data * data)59 void php_dom_private_data_destroy(php_dom_private_data *data)
60 {
61 	zend_hash_destroy(&data->ns_mapper.uri_to_prefix_map);
62 	if (data->template_fragments != NULL) {
63 		xmlNodePtr node;
64 		ZEND_HASH_MAP_FOREACH_PTR(data->template_fragments, node) {
65 			xmlFreeNode(node);
66 		} ZEND_HASH_FOREACH_END();
67 		zend_hash_destroy(data->template_fragments);
68 		FREE_HASHTABLE(data->template_fragments);
69 	}
70 	efree(data);
71 }
72 
php_dom_free_templated_content(php_dom_private_data * private_data,xmlNodePtr base)73 static void php_dom_free_templated_content(php_dom_private_data *private_data, xmlNodePtr base)
74 {
75 	/* Note: it's not possible to obtain a userland reference to these yet, so we can just free them without worrying
76 	 *       about their proxies.
77 	 * Note 2: it's possible to have nested template content. */
78 
79 	if (zend_hash_num_elements(private_data->template_fragments) > 0) {
80 		/* There's more templated content, try to free it. */
81 		xmlNodePtr current = base->children;
82 		while (current != NULL) {
83 			if (current->type == XML_ELEMENT_NODE) {
84 				php_dom_remove_templated_content(private_data, current);
85 			}
86 
87 			current = php_dom_next_in_tree_order(current, base);
88 		}
89 	}
90 
91 	xmlFreeNode(base);
92 }
93 
php_dom_add_templated_content(php_dom_private_data * private_data,const xmlNode * template_node,xmlNodePtr fragment)94 void php_dom_add_templated_content(php_dom_private_data *private_data, const xmlNode *template_node, xmlNodePtr fragment)
95 {
96 	if (private_data->template_fragments == NULL) {
97 		ALLOC_HASHTABLE(private_data->template_fragments);
98 		zend_hash_init(private_data->template_fragments, 0, NULL, NULL, false);
99 		zend_hash_real_init_mixed(private_data->template_fragments);
100 	}
101 
102 	zend_hash_index_add_new_ptr(private_data->template_fragments, dom_mangle_pointer_for_key(template_node), fragment);
103 }
104 
php_dom_retrieve_templated_content(php_dom_private_data * private_data,const xmlNode * template_node)105 xmlNodePtr php_dom_retrieve_templated_content(php_dom_private_data *private_data, const xmlNode *template_node)
106 {
107 	if (private_data->template_fragments == NULL) {
108 		return NULL;
109 	}
110 
111 	return zend_hash_index_find_ptr(private_data->template_fragments, dom_mangle_pointer_for_key(template_node));
112 }
113 
php_dom_ensure_templated_content(php_dom_private_data * private_data,xmlNodePtr template_node)114 xmlNodePtr php_dom_ensure_templated_content(php_dom_private_data *private_data, xmlNodePtr template_node)
115 {
116 	xmlNodePtr result = php_dom_retrieve_templated_content(private_data, template_node);
117 	if (result == NULL) {
118 		result = xmlNewDocFragment(template_node->doc);
119 		if (EXPECTED(result != NULL)) {
120 			result->parent = template_node;
121 			dom_add_element_ns_hook(private_data, template_node);
122 			php_dom_add_templated_content(private_data, template_node, result);
123 		}
124 	}
125 	return result;
126 }
127 
php_dom_remove_templated_content(php_dom_private_data * private_data,const xmlNode * template_node)128 void php_dom_remove_templated_content(php_dom_private_data *private_data, const xmlNode *template_node)
129 {
130 	if (private_data->template_fragments != NULL) {
131 		/* Deletion needs to be done not via a destructor because we can't access private_data from there. */
132 		zval *zv = zend_hash_index_find(private_data->template_fragments, dom_mangle_pointer_for_key(template_node));
133 		if (zv != NULL) {
134 			xmlNodePtr node = Z_PTR_P(zv);
135 			ZEND_ASSERT(offsetof(Bucket, val) == 0 && "Type cast only works if this is true");
136 			Bucket* bucket = (Bucket*) zv;
137 			/* First remove it from the bucket before freeing the content, otherwise recursion could make the bucket
138 			 * pointer invalid due to hash table structure changes. */
139 			zend_hash_del_bucket(private_data->template_fragments, bucket);
140 			php_dom_free_templated_content(private_data, node);
141 		}
142 	}
143 }
144 
php_dom_get_template_count(const php_dom_private_data * private_data)145 uint32_t php_dom_get_template_count(const php_dom_private_data *private_data)
146 {
147 	if (private_data->template_fragments != NULL) {
148 		return zend_hash_num_elements(private_data->template_fragments);
149 	} else {
150 		return 0;
151 	}
152 }
153 
dom_add_element_ns_hook(php_dom_private_data * private_data,xmlNodePtr element)154 void dom_add_element_ns_hook(php_dom_private_data *private_data, xmlNodePtr element)
155 {
156 	xmlNsPtr ns = pemalloc(sizeof(*ns), true);
157 
158 	/* The private data is a tagged data structure where only tag 1 is defined by ext/libxml to register a hook. */
159 	memset(ns, 0, sizeof(*ns));
160 	ns->prefix = xmlStrdup(element->ns->prefix);
161 	ns->href = xmlStrdup(element->ns->href);
162 	ns->type = XML_LOCAL_NAMESPACE;
163 	ns->_private = (void *) ((uintptr_t) private_data | LIBXML_NS_TAG_HOOK);
164 	element->ns = ns;
165 
166 	php_libxml_set_old_ns(element->doc, ns);
167 }
168 
169 #endif  /* HAVE_LIBXML && HAVE_DOM */
170