xref: /php-src/ext/dom/namespace_compat.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 "namespace_compat.h"
25 #include "private_data.h"
26 #include "internal_helpers.h"
27 
28 /* The actual value of these doesn't matter as long as they serve as a unique ID.
29  * They need to be pointers because the `_private` field is a pointer, however we can choose the contents ourselves.
30  * We need keep these at least 4-byte aligned because the pointer may be tagged (although for now 2 byte alignment works too).
31  * We use a trick: we declare a struct with a double member to force the alignment. */
32 #define DECLARE_NS_TOKEN(name, uri)			\
33 	static const struct {                   \
34 		char val[sizeof(uri)];				\
35 		double align;						\
36 	} decl_##name = { uri, 0.0 };			\
37 	PHP_DOM_EXPORT const php_dom_ns_magic_token *(name) = (const php_dom_ns_magic_token *) &decl_##name;
38 DECLARE_NS_TOKEN(php_dom_ns_is_html_magic_token, DOM_XHTML_NS_URI);
39 DECLARE_NS_TOKEN(php_dom_ns_is_mathml_magic_token, DOM_MATHML_NS_URI);
40 DECLARE_NS_TOKEN(php_dom_ns_is_svg_magic_token, DOM_SVG_NS_URI);
41 DECLARE_NS_TOKEN(php_dom_ns_is_xlink_magic_token, DOM_XLINK_NS_URI);
42 DECLARE_NS_TOKEN(php_dom_ns_is_xml_magic_token, DOM_XML_NS_URI);
43 DECLARE_NS_TOKEN(php_dom_ns_is_xmlns_magic_token, DOM_XMLNS_NS_URI);
44 
php_dom_libxml_ns_mapper_prefix_map_element_dtor(zval * zv)45 static void php_dom_libxml_ns_mapper_prefix_map_element_dtor(zval *zv)
46 {
47 	if (DOM_Z_IS_OWNED(zv)) {
48 		efree(Z_PTR_P(zv));
49 	}
50 }
51 
php_dom_libxml_ns_mapper_ensure_prefix_map(php_dom_libxml_ns_mapper * mapper,zend_string ** uri)52 static HashTable *php_dom_libxml_ns_mapper_ensure_prefix_map(php_dom_libxml_ns_mapper *mapper, zend_string **uri)
53 {
54 	zval *zv = zend_hash_find(&mapper->uri_to_prefix_map, *uri);
55 	HashTable *prefix_map;
56 	if (zv == NULL) {
57 		prefix_map = emalloc(sizeof(HashTable));
58 		zend_hash_init(prefix_map, 0, NULL, php_dom_libxml_ns_mapper_prefix_map_element_dtor, false);
59 		zval zv_prefix_map;
60 		ZVAL_ARR(&zv_prefix_map, prefix_map);
61 		zend_hash_add_new(&mapper->uri_to_prefix_map, *uri, &zv_prefix_map);
62 	} else {
63 		/* cast to Bucket* only works if this holds, I would prefer a static assert but we're stuck at C99. */
64 		ZEND_ASSERT(XtOffsetOf(Bucket, val) == 0);
65 		ZEND_ASSERT(Z_TYPE_P(zv) == IS_ARRAY);
66 		Bucket *bucket = (Bucket *) zv;
67 		/* Make sure we take the value from the key string that lives long enough. */
68 		*uri = bucket->key;
69 		prefix_map = Z_ARRVAL_P(zv);
70 	}
71 	return prefix_map;
72 }
73 
php_dom_libxml_ns_mapper_ensure_cached_ns(php_dom_libxml_ns_mapper * mapper,xmlNsPtr * ptr,const char * uri,size_t length,const php_dom_ns_magic_token * token)74 static xmlNsPtr php_dom_libxml_ns_mapper_ensure_cached_ns(php_dom_libxml_ns_mapper *mapper, xmlNsPtr *ptr, const char *uri, size_t length, const php_dom_ns_magic_token *token)
75 {
76 	if (EXPECTED(*ptr != NULL)) {
77 		return *ptr;
78 	}
79 
80 	zend_string *uri_str = zend_string_init(uri, length, false);
81 	*ptr = php_dom_libxml_ns_mapper_get_ns(mapper, NULL, uri_str);
82 	(*ptr)->_private = (void *) token;
83 	zend_string_release_ex(uri_str, false);
84 	return *ptr;
85 }
86 
php_dom_libxml_ns_mapper_ensure_html_ns(php_dom_libxml_ns_mapper * mapper)87 PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_ensure_html_ns(php_dom_libxml_ns_mapper *mapper)
88 {
89 	return php_dom_libxml_ns_mapper_ensure_cached_ns(mapper, &mapper->html_ns, DOM_XHTML_NS_URI, sizeof(DOM_XHTML_NS_URI) - 1, php_dom_ns_is_html_magic_token);
90 }
91 
php_dom_libxml_ns_mapper_ensure_prefixless_xmlns_ns(php_dom_libxml_ns_mapper * mapper)92 PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_ensure_prefixless_xmlns_ns(php_dom_libxml_ns_mapper *mapper)
93 {
94 	return php_dom_libxml_ns_mapper_ensure_cached_ns(mapper, &mapper->prefixless_xmlns_ns, DOM_XMLNS_NS_URI, sizeof(DOM_XMLNS_NS_URI) - 1, php_dom_ns_is_xmlns_magic_token);
95 }
96 
dom_create_owned_ns(zend_string * prefix,zend_string * uri)97 static xmlNsPtr dom_create_owned_ns(zend_string *prefix, zend_string *uri)
98 {
99 	ZEND_ASSERT(prefix != NULL);
100 	ZEND_ASSERT(uri != NULL);
101 
102 	xmlNsPtr ns = emalloc(sizeof(*ns));
103 	memset(ns, 0, sizeof(*ns));
104 	ns->type = XML_LOCAL_NAMESPACE;
105 	/* These two strings are kept alive because they're the hash table keys that lead to this entry. */
106 	ns->prefix = ZSTR_LEN(prefix) == 0 ? NULL : BAD_CAST ZSTR_VAL(prefix);
107 	ns->href = BAD_CAST ZSTR_VAL(uri);
108 	/* Note ns->context is unused in libxml2 at the moment, and if it were used it would be for
109 	 * LIBXML_NAMESPACE_DICT which is opt-in anyway. */
110 
111 	return ns;
112 }
113 
php_dom_libxml_ns_mapper_get_ns(php_dom_libxml_ns_mapper * mapper,zend_string * prefix,zend_string * uri)114 PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_get_ns(php_dom_libxml_ns_mapper *mapper, zend_string *prefix, zend_string *uri)
115 {
116 	if (uri == NULL) {
117 		uri = zend_empty_string;
118 	}
119 
120 	if (prefix == NULL) {
121 		prefix = zend_empty_string;
122 	}
123 
124 	if (ZSTR_LEN(prefix) == 0 && ZSTR_LEN(uri) == 0) {
125 		return NULL;
126 	}
127 
128 	HashTable *prefix_map = php_dom_libxml_ns_mapper_ensure_prefix_map(mapper, &uri);
129 	xmlNsPtr found = zend_hash_find_ptr(prefix_map, prefix);
130 	if (found != NULL) {
131 		return found;
132 	}
133 
134 	xmlNsPtr ns = dom_create_owned_ns(prefix, uri);
135 
136 	zval new_zv;
137 	DOM_Z_OWNED(&new_zv, ns);
138 	zend_hash_add_new(prefix_map, prefix, &new_zv);
139 
140 	return ns;
141 }
142 
php_dom_libxml_ns_mapper_get_ns_raw_prefix_string(php_dom_libxml_ns_mapper * mapper,const xmlChar * prefix,size_t prefix_len,zend_string * uri)143 PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_get_ns_raw_prefix_string(php_dom_libxml_ns_mapper *mapper, const xmlChar *prefix, size_t prefix_len, zend_string *uri)
144 {
145 	xmlNsPtr ns;
146 	if (prefix_len == 0) {
147 		/* Fast path */
148 		ns = php_dom_libxml_ns_mapper_get_ns(mapper, zend_empty_string, uri);
149 	} else {
150 		zend_string *prefix_str = zend_string_init((const char *) prefix, prefix_len, false);
151 		ns = php_dom_libxml_ns_mapper_get_ns(mapper, prefix_str, uri);
152 		zend_string_release_ex(prefix_str, false);
153 	}
154 	return ns;
155 }
156 
php_dom_libxml_ns_mapper_get_ns_raw_strings_ex(php_dom_libxml_ns_mapper * mapper,const char * prefix,size_t prefix_len,const char * uri,size_t uri_len)157 static xmlNsPtr php_dom_libxml_ns_mapper_get_ns_raw_strings_ex(php_dom_libxml_ns_mapper *mapper, const char *prefix, size_t prefix_len, const char *uri, size_t uri_len)
158 {
159 	zend_string *prefix_str = zend_string_init(prefix, prefix_len, false);
160 	zend_string *uri_str = zend_string_init(uri, uri_len, false);
161 	xmlNsPtr ns = php_dom_libxml_ns_mapper_get_ns(mapper, prefix_str, uri_str);
162 	zend_string_release_ex(prefix_str, false);
163 	zend_string_release_ex(uri_str, false);
164 	return ns;
165 }
166 
php_dom_libxml_ns_mapper_get_ns_raw_strings(php_dom_libxml_ns_mapper * mapper,const char * prefix,const char * uri)167 static zend_always_inline xmlNsPtr php_dom_libxml_ns_mapper_get_ns_raw_strings(php_dom_libxml_ns_mapper *mapper, const char *prefix, const char *uri)
168 {
169 	return php_dom_libxml_ns_mapper_get_ns_raw_strings_ex(mapper, prefix, strlen(prefix), uri, strlen(uri));
170 }
171 
php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(php_dom_libxml_ns_mapper * mapper,const char * prefix,const char * uri)172 PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(php_dom_libxml_ns_mapper *mapper, const char *prefix, const char *uri)
173 {
174 	if (prefix == NULL) {
175 		prefix = "";
176 	}
177 	if (uri == NULL) {
178 		uri = "";
179 	}
180 	return php_dom_libxml_ns_mapper_get_ns_raw_strings(mapper, prefix, uri);
181 }
182 
php_dom_libxml_ns_mapper_store_and_normalize_parsed_ns(php_dom_libxml_ns_mapper * mapper,xmlNsPtr ns)183 static xmlNsPtr php_dom_libxml_ns_mapper_store_and_normalize_parsed_ns(php_dom_libxml_ns_mapper *mapper, xmlNsPtr ns)
184 {
185 	ZEND_ASSERT(ns != NULL);
186 
187 	zend_string *href_str = zend_string_init((const char *) ns->href, xmlStrlen(ns->href), false);
188 	zend_string *href_str_orig = href_str;
189 	HashTable *prefix_map = php_dom_libxml_ns_mapper_ensure_prefix_map(mapper, &href_str);
190 	zend_string_release_ex(href_str_orig, false);
191 
192 	const char *prefix = (const char *) ns->prefix;
193 	size_t prefix_len;
194 	if (prefix == NULL) {
195 		prefix = "";
196 		prefix_len = 0;
197 	} else {
198 		prefix_len = xmlStrlen(ns->prefix);
199 	}
200 
201 	zval *zv = zend_hash_str_find_ptr(prefix_map, prefix, prefix_len);
202 	if (zv != NULL) {
203 		return Z_PTR_P(zv);
204 	}
205 
206 	zval new_zv;
207 	DOM_Z_UNOWNED(&new_zv, ns);
208 	zend_hash_str_add_new(prefix_map, prefix, prefix_len, &new_zv);
209 
210 	return ns;
211 }
212 
213 typedef struct {
214 	/* Fast lookup for created mappings. */
215 	HashTable old_ns_to_new_ns_ptr;
216 	/* It is common that the last created mapping will be used for a while,
217 	 * cache it too to bypass the hash table. */
218 	xmlNsPtr last_mapped_src, last_mapped_dst;
219 	php_dom_libxml_ns_mapper *ns_mapper;
220 } dom_libxml_reconcile_ctx;
221 
php_dom_get_ns_mapper(dom_object * object)222 PHP_DOM_EXPORT php_dom_libxml_ns_mapper *php_dom_get_ns_mapper(dom_object *object)
223 {
224 	return &php_dom_get_private_data(object)->ns_mapper;
225 }
226 
php_dom_ns_compat_mark_attribute(php_dom_libxml_ns_mapper * mapper,xmlNodePtr node,xmlNsPtr ns)227 PHP_DOM_EXPORT xmlAttrPtr php_dom_ns_compat_mark_attribute(php_dom_libxml_ns_mapper *mapper, xmlNodePtr node, xmlNsPtr ns)
228 {
229 	xmlNsPtr xmlns_ns;
230 	const xmlChar *name;
231 	if (ns->prefix != NULL) {
232 		xmlns_ns = php_dom_libxml_ns_mapper_get_ns_raw_strings(mapper, "xmlns", DOM_XMLNS_NS_URI);
233 		name = ns->prefix;
234 	} else {
235 		xmlns_ns = php_dom_libxml_ns_mapper_ensure_prefixless_xmlns_ns(mapper);
236 		name = BAD_CAST "xmlns";
237 	}
238 
239 	ZEND_ASSERT(xmlns_ns != NULL);
240 
241 	return xmlSetNsProp(node, xmlns_ns, name, ns->href);
242 }
243 
php_dom_ns_compat_mark_attribute_list(php_dom_libxml_ns_mapper * mapper,xmlNodePtr node)244 PHP_DOM_EXPORT void php_dom_ns_compat_mark_attribute_list(php_dom_libxml_ns_mapper *mapper, xmlNodePtr node)
245 {
246 	if (node->nsDef == NULL) {
247 		return;
248 	}
249 
250 	/* We want to prepend at the front, but in order of the namespace definitions.
251 	 * So temporarily unlink the existing properties and add them again at the end. */
252 	xmlAttrPtr attr = node->properties;
253 	node->properties = NULL;
254 
255 	xmlNsPtr ns = node->nsDef;
256 	xmlAttrPtr last_added = NULL;
257 	do {
258 		last_added = php_dom_ns_compat_mark_attribute(mapper, node, ns);
259 		php_dom_libxml_ns_mapper_store_and_normalize_parsed_ns(mapper, ns);
260 		xmlNsPtr next = ns->next;
261 		ns->next = NULL;
262 		php_libxml_set_old_ns(node->doc, ns);
263 		ns = next;
264 	} while (ns != NULL);
265 
266 	if (last_added != NULL) {
267 		/* node->properties now points to the first namespace declaration attribute. */
268 		if (attr != NULL) {
269 			last_added->next = attr;
270 			attr->prev = last_added;
271 		}
272 	} else {
273 		/* Nothing added, so nothing changed. Only really possible on OOM. */
274 		node->properties = attr;
275 	}
276 
277 	node->nsDef = NULL;
278 }
279 
php_dom_ns_is_fast_ex(xmlNsPtr ns,const php_dom_ns_magic_token * magic_token)280 PHP_DOM_EXPORT bool php_dom_ns_is_fast_ex(xmlNsPtr ns, const php_dom_ns_magic_token *magic_token)
281 {
282 	ZEND_ASSERT(ns != NULL);
283 	/* cached for fast checking */
284 	if (ns->_private == magic_token) {
285 		return true;
286 	} else if (ns->_private != NULL && ((uintptr_t) ns->_private & 1) == 0) {
287 		/* Other token stored */
288 		return false;
289 	}
290 	/* Slow path */
291 	if (xmlStrEqual(ns->href, BAD_CAST magic_token)) {
292 		if (ns->_private == NULL) {
293 			/* Only overwrite the private data if there is no other token stored. */
294 			ns->_private = (void *) magic_token;
295 		}
296 		return true;
297 	}
298 	return false;
299 }
300 
php_dom_ns_is_fast(const xmlNode * nodep,const php_dom_ns_magic_token * magic_token)301 PHP_DOM_EXPORT bool php_dom_ns_is_fast(const xmlNode *nodep, const php_dom_ns_magic_token *magic_token)
302 {
303 	ZEND_ASSERT(nodep != NULL);
304 	xmlNsPtr ns = nodep->ns;
305 	if (ns != NULL) {
306 		return php_dom_ns_is_fast_ex(ns, magic_token);
307 	}
308 	return false;
309 }
310 
php_dom_ns_is_html_and_document_is_html(const xmlNode * nodep)311 PHP_DOM_EXPORT bool php_dom_ns_is_html_and_document_is_html(const xmlNode *nodep)
312 {
313 	ZEND_ASSERT(nodep != NULL);
314 	return nodep->doc && nodep->doc->type == XML_HTML_DOCUMENT_NODE && php_dom_ns_is_fast(nodep, php_dom_ns_is_html_magic_token);
315 }
316 
317 /* will rename prefixes if there is a declaration with the same prefix but different uri. */
php_dom_reconcile_attribute_namespace_after_insertion(xmlAttrPtr attrp)318 PHP_DOM_EXPORT void php_dom_reconcile_attribute_namespace_after_insertion(xmlAttrPtr attrp)
319 {
320 	ZEND_ASSERT(attrp != NULL);
321 
322 	if (attrp->ns != NULL) {
323 		/* Try to link to an existing namespace. If that won't work, reconcile. */
324 		xmlNodePtr nodep = attrp->parent;
325 		xmlNsPtr matching_ns = xmlSearchNs(nodep->doc, nodep, attrp->ns->prefix);
326 		if (matching_ns && xmlStrEqual(matching_ns->href, attrp->ns->href)) {
327 			/* Doesn't leak because this doesn't define the declaration. */
328 			attrp->ns = matching_ns;
329 		} else {
330 			if (attrp->ns->prefix != NULL) {
331 				/* Note: explicitly use the legacy reconciliation as it mostly (i.e. as good as it gets for legacy DOM)
332 				* does the right thing for attributes. */
333 				xmlReconciliateNs(nodep->doc, nodep);
334 			}
335 		}
336 	}
337 }
338 
php_dom_libxml_reconcile_modern_single_node(dom_libxml_reconcile_ctx * ctx,xmlNodePtr node)339 static zend_always_inline void php_dom_libxml_reconcile_modern_single_node(dom_libxml_reconcile_ctx *ctx, xmlNodePtr node)
340 {
341 	ZEND_ASSERT(node->ns != NULL);
342 
343 	if (node->ns == ctx->last_mapped_src) {
344 		node->ns = ctx->last_mapped_dst;
345 		return;
346 	}
347 
348 	/* If the namespace is the same as in the map, we're good. */
349 	xmlNsPtr new_ns = zend_hash_index_find_ptr(&ctx->old_ns_to_new_ns_ptr, dom_mangle_pointer_for_key(node->ns));
350 	if (new_ns == NULL) {
351 		/* We have to create an alternative declaration, and we'll add it to the map. */
352 		const char *prefix = (const char *) node->ns->prefix;
353 		const char *href = (const char *) node->ns->href;
354 		new_ns = php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ctx->ns_mapper, prefix, href);
355 		zend_hash_index_add_new_ptr(&ctx->old_ns_to_new_ns_ptr, dom_mangle_pointer_for_key(node->ns), new_ns);
356 		ctx->last_mapped_src = node->ns;
357 		ctx->last_mapped_dst = new_ns;
358 		node->ns = new_ns;
359 	} else if (node->ns != new_ns) {
360 		/* The namespace is different, so we have to replace it. */
361 		node->ns = new_ns;
362 	}
363 }
364 
dom_libxml_reconcile_fast_element_skip(xmlNodePtr node)365 static zend_always_inline bool dom_libxml_reconcile_fast_element_skip(xmlNodePtr node)
366 {
367 	/* Fast path: this is a lone element and the namespace is defined by the node (or the namespace is NULL). */
368 	ZEND_ASSERT(node->type == XML_ELEMENT_NODE);
369 	return node->children == NULL && node->properties == NULL && node->ns == node->nsDef;
370 }
371 
php_dom_libxml_reconcile_modern_single_element_node(dom_libxml_reconcile_ctx * ctx,xmlNodePtr node)372 static zend_always_inline void php_dom_libxml_reconcile_modern_single_element_node(dom_libxml_reconcile_ctx *ctx, xmlNodePtr node)
373 {
374 	ZEND_ASSERT(node->type == XML_ELEMENT_NODE);
375 
376 	/* Since this is modern DOM, the declarations are not on the node and thus there's nothing to add from nsDef. */
377 	ZEND_ASSERT(node->nsDef == NULL);
378 
379 	if (node->ns != NULL) {
380 		php_dom_libxml_reconcile_modern_single_node(ctx, node);
381 	}
382 
383 	for (xmlAttrPtr attr = node->properties; attr != NULL; attr = attr->next) {
384 		if (attr->ns != NULL) {
385 			php_dom_libxml_reconcile_modern_single_node(ctx, (xmlNodePtr) attr);
386 		}
387 	}
388 }
389 
php_dom_libxml_reconcile_modern(php_dom_libxml_ns_mapper * ns_mapper,xmlNodePtr node)390 PHP_DOM_EXPORT void php_dom_libxml_reconcile_modern(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node)
391 {
392 	if (node->type == XML_ATTRIBUTE_NODE) {
393 		if (node->ns != NULL) {
394 			node->ns = php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, (const char *) node->ns->prefix, (const char *) node->ns->href);
395 		}
396 		return;
397 	}
398 
399 	if (node->type != XML_ELEMENT_NODE || dom_libxml_reconcile_fast_element_skip(node)) {
400 		return;
401 	}
402 
403 	dom_libxml_reconcile_ctx ctx;
404 	zend_hash_init(&ctx.old_ns_to_new_ns_ptr, 0, NULL, NULL, 0);
405 	ctx.last_mapped_src = NULL;
406 	ctx.last_mapped_dst = NULL;
407 	ctx.ns_mapper = ns_mapper;
408 
409 	php_dom_libxml_reconcile_modern_single_element_node(&ctx, node);
410 
411 	xmlNodePtr base = node;
412 	node = node->children;
413 	while (node != NULL) {
414 		ZEND_ASSERT(node != base);
415 
416 		if (node->type == XML_ELEMENT_NODE) {
417 			php_dom_libxml_reconcile_modern_single_element_node(&ctx, node);
418 		}
419 
420 		node = php_dom_next_in_tree_order(node, base);
421 	}
422 
423 	zend_hash_destroy(&ctx.old_ns_to_new_ns_ptr);
424 }
425 
php_dom_get_in_scope_ns(php_dom_libxml_ns_mapper * ns_mapper,const xmlNode * node,bool ignore_elements)426 PHP_DOM_EXPORT php_dom_in_scope_ns php_dom_get_in_scope_ns(php_dom_libxml_ns_mapper *ns_mapper, const xmlNode *node, bool ignore_elements)
427 {
428 	ZEND_ASSERT(node != NULL);
429 
430 	php_dom_in_scope_ns in_scope_ns;
431 	in_scope_ns.origin_is_ns_compat = true;
432 
433 	/* libxml fetches all nsDef items from bottom to top - left to right, ignoring prefixes already in the list.
434 	 * We don't have nsDef, but we can use the ns pointer (as that is necessarily in scope),
435 	 * and check the xmlns attributes. */
436 	HashTable tmp_prefix_to_ns_table;
437 	zend_hash_init(&tmp_prefix_to_ns_table, 0, NULL, NULL, false);
438 	zend_hash_real_init_mixed(&tmp_prefix_to_ns_table);
439 
440 	for (const xmlNode *cur = node; cur != NULL; cur = cur->parent) {
441 		if (cur->type == XML_ELEMENT_NODE) {
442 			/* Register namespace of element */
443 			if (!ignore_elements && cur->ns != NULL && cur->ns->prefix != NULL) {
444 				const char *prefix = (const char *) cur->ns->prefix;
445 				zend_hash_str_add_ptr(&tmp_prefix_to_ns_table, prefix, strlen(prefix), cur->ns);
446 			}
447 
448 			/* Register xmlns attributes */
449 			for (const xmlAttr *attr = cur->properties; attr != NULL; attr = attr->next) {
450 				if (attr->ns != NULL && attr->ns->prefix != NULL && php_dom_ns_is_fast_ex(attr->ns, php_dom_ns_is_xmlns_magic_token)
451 					&& attr->children != NULL && attr->children->content != NULL) {
452 					/* This attribute declares a namespace, get the relevant instance.
453 					 * The declared namespace is not the same as the namespace of this attribute (which is xmlns). */
454 					const char *prefix = (const char *) attr->name;
455 					xmlNsPtr ns = php_dom_libxml_ns_mapper_get_ns_raw_strings(ns_mapper, prefix, (const char *) attr->children->content);
456 					zend_hash_str_add_ptr(&tmp_prefix_to_ns_table, prefix, strlen(prefix), ns);
457 				}
458 			}
459 		}
460 	}
461 
462 	in_scope_ns.count = zend_hash_num_elements(&tmp_prefix_to_ns_table);
463 	in_scope_ns.list = safe_emalloc(in_scope_ns.count, sizeof(xmlNsPtr), 0);
464 
465 	size_t index = 0;
466 	xmlNsPtr ns;
467 	ZEND_HASH_MAP_FOREACH_PTR(&tmp_prefix_to_ns_table, ns) {
468 		in_scope_ns.list[index++] = ns;
469 	} ZEND_HASH_FOREACH_END();
470 
471 	zend_hash_destroy(&tmp_prefix_to_ns_table);
472 
473 	return in_scope_ns;
474 }
475 
php_dom_get_in_scope_ns_legacy(const xmlNode * node)476 PHP_DOM_EXPORT php_dom_in_scope_ns php_dom_get_in_scope_ns_legacy(const xmlNode *node)
477 {
478 	ZEND_ASSERT(node != NULL);
479 
480 	php_dom_in_scope_ns in_scope_ns;
481 	in_scope_ns.origin_is_ns_compat = false;
482 	in_scope_ns.list = xmlGetNsList(node->doc, node);
483 	in_scope_ns.count = 0;
484 
485 	if (in_scope_ns.list != NULL) {
486 		while (in_scope_ns.list[in_scope_ns.count] != NULL) {
487 			in_scope_ns.count++;
488 		}
489 	}
490 
491 	return in_scope_ns;
492 }
493 
php_dom_in_scope_ns_destroy(php_dom_in_scope_ns * in_scope_ns)494 PHP_DOM_EXPORT void php_dom_in_scope_ns_destroy(php_dom_in_scope_ns *in_scope_ns)
495 {
496 	ZEND_ASSERT(in_scope_ns != NULL);
497 	if (in_scope_ns->origin_is_ns_compat) {
498 		efree(in_scope_ns->list);
499 	} else {
500 		xmlFree(in_scope_ns->list);
501 	}
502 }
503 
504 #endif  /* HAVE_LIBXML && HAVE_DOM */
505