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