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