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 +----------------------------------------------------------------------+
16 */
17
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21
22 #include "php.h"
23 #if defined(HAVE_LIBXML) && defined(HAVE_DOM)
24 #include "php_dom.h"
25 #include "namespace_compat.h"
26 #include "internal_helpers.h"
27 #include "dom_properties.h"
28
29 /*
30 * class DOMNode
31 *
32 * URL: https://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1950641247
33 * Since:
34 */
35
dom_node_concatenated_name_helper(size_t name_len,const char * name,size_t prefix_len,const char * prefix)36 zend_string *dom_node_concatenated_name_helper(size_t name_len, const char *name, size_t prefix_len, const char *prefix)
37 {
38 /* prefix_len can't overflow because it would need to occupy the entire address space */
39 zend_string *str = zend_string_safe_alloc(1, name_len, prefix_len + 1, false);
40 memcpy(ZSTR_VAL(str), prefix, prefix_len);
41 ZSTR_VAL(str)[prefix_len] = ':';
42 memcpy(ZSTR_VAL(str) + prefix_len + 1, name, name_len + 1 /* include \0 */);
43 return str;
44 }
45
dom_node_get_node_name_attribute_or_element(const xmlNode * nodep,bool uppercase)46 zend_string *dom_node_get_node_name_attribute_or_element(const xmlNode *nodep, bool uppercase)
47 {
48 zend_string *ret;
49 size_t name_len = strlen((const char *) nodep->name);
50 if (nodep->ns != NULL && nodep->ns->prefix != NULL) {
51 ret = dom_node_concatenated_name_helper(name_len, (const char *) nodep->name, strlen((const char *) nodep->ns->prefix), (const char *) nodep->ns->prefix);
52 } else {
53 ret = zend_string_init((const char *) nodep->name, name_len, false);
54 }
55 if (uppercase) {
56 zend_str_toupper(ZSTR_VAL(ret), ZSTR_LEN(ret));
57 }
58 return ret;
59 }
60
php_dom_is_node_connected(const xmlNode * node)61 bool php_dom_is_node_connected(const xmlNode *node)
62 {
63 ZEND_ASSERT(node != NULL);
64 do {
65 if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) {
66 return true;
67 }
68 node = node->parent;
69 } while (node != NULL);
70 return false;
71 }
72
73 /* {{{ nodeName string
74 readonly=yes
75 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-F68D095
76 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-nodename
77 Since:
78 */
dom_node_node_name_read(dom_object * obj,zval * retval)79 zend_result dom_node_node_name_read(dom_object *obj, zval *retval)
80 {
81 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
82
83 bool uppercase = false;
84
85 switch (nodep->type) {
86 case XML_ELEMENT_NODE:
87 uppercase = php_dom_follow_spec_intern(obj) && php_dom_ns_is_html_and_document_is_html(nodep);
88 ZEND_FALLTHROUGH;
89 case XML_ATTRIBUTE_NODE:
90 ZVAL_NEW_STR(retval, dom_node_get_node_name_attribute_or_element(nodep, uppercase));
91 break;
92 case XML_NAMESPACE_DECL: {
93 xmlNsPtr ns = nodep->ns;
94 if (ns != NULL && ns->prefix) {
95 zend_string *str = dom_node_concatenated_name_helper(strlen((const char *) ns->prefix), (const char *) ns->prefix, strlen("xmlns"), "xmlns");
96 ZVAL_NEW_STR(retval, str);
97 } else {
98 ZVAL_STRING(retval, (const char *) nodep->name);
99 }
100 break;
101 }
102 case XML_DOCUMENT_TYPE_NODE:
103 case XML_DTD_NODE:
104 case XML_PI_NODE:
105 case XML_ENTITY_DECL:
106 case XML_ENTITY_REF_NODE:
107 case XML_NOTATION_NODE:
108 ZVAL_STRING(retval, (char *) nodep->name);
109 break;
110 case XML_CDATA_SECTION_NODE:
111 ZVAL_STRING(retval, "#cdata-section");
112 break;
113 case XML_COMMENT_NODE:
114 ZVAL_STRING(retval, "#comment");
115 break;
116 case XML_HTML_DOCUMENT_NODE:
117 case XML_DOCUMENT_NODE:
118 ZVAL_STRING(retval, "#document");
119 break;
120 case XML_DOCUMENT_FRAG_NODE:
121 ZVAL_STRING(retval, "#document-fragment");
122 break;
123 case XML_TEXT_NODE:
124 ZVAL_STRING(retval, "#text");
125 break;
126 EMPTY_SWITCH_DEFAULT_CASE();
127 }
128
129 return SUCCESS;
130 }
131
132 /* }}} */
133
134 /* {{{ nodeValue string
135 readonly=no
136 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-F68D080
137 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-nodevalue
138 Since:
139 */
dom_node_node_value_read(dom_object * obj,zval * retval)140 zend_result dom_node_node_value_read(dom_object *obj, zval *retval)
141 {
142 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
143
144 /* Access to Element node is implemented as a convenience method */
145 switch (nodep->type) {
146 case XML_ELEMENT_NODE: {
147 if (php_dom_follow_spec_intern(obj)) {
148 ZVAL_NULL(retval);
149 break;
150 }
151 ZEND_FALLTHROUGH;
152 }
153 case XML_ATTRIBUTE_NODE:
154 case XML_TEXT_NODE:
155 case XML_COMMENT_NODE:
156 case XML_CDATA_SECTION_NODE:
157 case XML_PI_NODE:
158 php_dom_get_content_into_zval(nodep, retval, true);
159 break;
160 case XML_NAMESPACE_DECL: {
161 char *str = (char *) xmlNodeGetContent(nodep->children);
162 if (str != NULL) {
163 ZVAL_STRING(retval, str);
164 xmlFree(str);
165 } else {
166 ZVAL_NULL(retval);
167 }
168 break;
169 }
170 default:
171 ZVAL_NULL(retval);
172 break;
173 }
174
175 return SUCCESS;
176 }
177
dom_node_node_value_write(dom_object * obj,zval * newval)178 zend_result dom_node_node_value_write(dom_object *obj, zval *newval)
179 {
180 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
181
182 /* Cannot fail because the type is either null or a string. */
183 zend_string *str = zval_get_string(newval);
184
185 /* Access to Element node is implemented as a convenience method */
186 switch (nodep->type) {
187 case XML_ATTRIBUTE_NODE:
188 if (php_dom_follow_spec_intern(obj)) {
189 dom_remove_all_children(nodep);
190 xmlAddChild(nodep, xmlNewTextLen(BAD_CAST ZSTR_VAL(str), ZSTR_LEN(str)));
191 break;
192 }
193 ZEND_FALLTHROUGH;
194 case XML_ELEMENT_NODE:
195 dom_remove_all_children(nodep);
196 ZEND_FALLTHROUGH;
197 case XML_TEXT_NODE:
198 case XML_COMMENT_NODE:
199 case XML_CDATA_SECTION_NODE:
200 case XML_PI_NODE:
201 xmlNodeSetContentLen(nodep, BAD_CAST ZSTR_VAL(str), ZSTR_LEN(str));
202 break;
203 default:
204 break;
205 }
206
207 php_libxml_invalidate_node_list_cache(obj->document);
208
209 zend_string_release_ex(str, 0);
210 return SUCCESS;
211 }
212
213 /* }}} */
214
215 /* {{{ nodeType int
216 readonly=yes
217 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-111237558
218 Since:
219 */
dom_node_node_type_read(dom_object * obj,zval * retval)220 zend_result dom_node_node_type_read(dom_object *obj, zval *retval)
221 {
222 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
223
224 /* Specs dictate that they are both type XML_DOCUMENT_TYPE_NODE */
225 if (nodep->type == XML_DTD_NODE) {
226 ZVAL_LONG(retval, XML_DOCUMENT_TYPE_NODE);
227 } else {
228 ZVAL_LONG(retval, nodep->type);
229 }
230
231 return SUCCESS;
232 }
233
234 /* }}} */
235
dom_node_parent_get(dom_object * obj,zval * retval,bool only_element)236 static zend_result dom_node_parent_get(dom_object *obj, zval *retval, bool only_element)
237 {
238 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
239
240 xmlNodePtr nodeparent = nodep->parent;
241 if (!nodeparent || (only_element && nodeparent->type != XML_ELEMENT_NODE)) {
242 ZVAL_NULL(retval);
243 return SUCCESS;
244 }
245
246 php_dom_create_object(nodeparent, retval, obj);
247 return SUCCESS;
248 }
249
250 /* {{{ parentNode ?DomNode
251 readonly=yes
252 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1060184317
253 Since:
254 */
dom_node_parent_node_read(dom_object * obj,zval * retval)255 zend_result dom_node_parent_node_read(dom_object *obj, zval *retval)
256 {
257 return dom_node_parent_get(obj, retval, false);
258 }
259
260 /* }}} */
261
262 /* {{{ parentElement ?DomElement
263 readonly=yes
264 URL: https://dom.spec.whatwg.org/#parent-element
265 Since:
266 */
dom_node_parent_element_read(dom_object * obj,zval * retval)267 zend_result dom_node_parent_element_read(dom_object *obj, zval *retval)
268 {
269 return dom_node_parent_get(obj, retval, true);
270 }
271
272 /* }}} */
273
274 /* {{{ childNodes DomNodeList
275 readonly=yes
276 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1451460987
277 Since:
278 */
dom_node_child_nodes_read(dom_object * obj,zval * retval)279 zend_result dom_node_child_nodes_read(dom_object *obj, zval *retval)
280 {
281 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
282
283 php_dom_create_iterator(retval, DOM_NODELIST, php_dom_follow_spec_intern(obj));
284 dom_object *intern = Z_DOMOBJ_P(retval);
285 dom_namednode_iter(obj, XML_ELEMENT_NODE, intern, NULL, NULL, 0, NULL, 0);
286
287 return SUCCESS;
288 }
289 /* }}} */
290
291 /* {{{ firstChild DomNode
292 readonly=yes
293 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-169727388
294 Since:
295 */
dom_node_first_child_read(dom_object * obj,zval * retval)296 zend_result dom_node_first_child_read(dom_object *obj, zval *retval)
297 {
298 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
299
300 xmlNodePtr first = NULL;
301 if (dom_node_children_valid(nodep)) {
302 first = nodep->children;
303 }
304
305 if (!first) {
306 ZVAL_NULL(retval);
307 return SUCCESS;
308 }
309
310 php_dom_create_object(first, retval, obj);
311 return SUCCESS;
312 }
313
314 /* }}} */
315
316 /* {{{ lastChild DomNode
317 readonly=yes
318 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-61AD09FB
319 Since:
320 */
dom_node_last_child_read(dom_object * obj,zval * retval)321 zend_result dom_node_last_child_read(dom_object *obj, zval *retval)
322 {
323 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
324
325 xmlNodePtr last = NULL;
326 if (dom_node_children_valid(nodep)) {
327 last = nodep->last;
328 }
329
330 if (!last) {
331 ZVAL_NULL(retval);
332 return SUCCESS;
333 }
334
335 php_dom_create_object(last, retval, obj);
336 return SUCCESS;
337 }
338
339 /* }}} */
340
341 /* {{{ previousSibling DomNode
342 readonly=yes
343 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-640FB3C8
344 Since:
345 */
dom_node_previous_sibling_read(dom_object * obj,zval * retval)346 zend_result dom_node_previous_sibling_read(dom_object *obj, zval *retval)
347 {
348 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
349
350 xmlNodePtr prevsib = nodep->prev;
351 if (!prevsib) {
352 ZVAL_NULL(retval);
353 return SUCCESS;
354 }
355
356 php_dom_create_object(prevsib, retval, obj);
357 return SUCCESS;
358 }
359
360 /* }}} */
361
362 /* {{{ nextSibling DomNode
363 readonly=yes
364 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-6AC54C2F
365 Since:
366 */
dom_node_next_sibling_read(dom_object * obj,zval * retval)367 zend_result dom_node_next_sibling_read(dom_object *obj, zval *retval)
368 {
369 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
370
371 xmlNodePtr nextsib = nodep->next;
372 if (!nextsib) {
373 ZVAL_NULL(retval);
374 return SUCCESS;
375 }
376
377 php_dom_create_object(nextsib, retval, obj);
378 return SUCCESS;
379 }
380
381 /* }}} */
382
383 /* {{{ previousElementSibling DomNode
384 readonly=yes
385 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-640FB3C8
386 Since:
387 */
dom_node_previous_element_sibling_read(dom_object * obj,zval * retval)388 zend_result dom_node_previous_element_sibling_read(dom_object *obj, zval *retval)
389 {
390 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
391
392 xmlNodePtr prevsib = nodep->prev;
393
394 while (prevsib && prevsib->type != XML_ELEMENT_NODE) {
395 prevsib = prevsib->prev;
396 }
397
398 if (!prevsib) {
399 ZVAL_NULL(retval);
400 return SUCCESS;
401 }
402
403 php_dom_create_object(prevsib, retval, obj);
404 return SUCCESS;
405 }
406
407 /* }}} */
408
409 /* {{{ nextElementSibling DomNode
410 readonly=yes
411 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-6AC54C2F
412 Since:
413 */
dom_node_next_element_sibling_read(dom_object * obj,zval * retval)414 zend_result dom_node_next_element_sibling_read(dom_object *obj, zval *retval)
415 {
416 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
417
418 xmlNodePtr nextsib = nodep->next;
419
420 while (nextsib != NULL && nextsib->type != XML_ELEMENT_NODE) {
421 nextsib = nextsib->next;
422 }
423
424 if (!nextsib) {
425 ZVAL_NULL(retval);
426 return SUCCESS;
427 }
428
429 php_dom_create_object(nextsib, retval, obj);
430 return SUCCESS;
431 }
432
433 /* }}} */
434
435 /* {{{ attributes DomNamedNodeMap
436 readonly=yes
437 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-84CF096
438 Since:
439 */
dom_node_attributes_read(dom_object * obj,zval * retval)440 zend_result dom_node_attributes_read(dom_object *obj, zval *retval)
441 {
442 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
443
444 if (nodep->type == XML_ELEMENT_NODE) {
445 php_dom_create_iterator(retval, DOM_NAMEDNODEMAP, php_dom_follow_spec_intern(obj));
446 dom_object *intern = Z_DOMOBJ_P(retval);
447 dom_namednode_iter(obj, XML_ATTRIBUTE_NODE, intern, NULL, NULL, 0, NULL, 0);
448 } else {
449 ZVAL_NULL(retval);
450 }
451
452 return SUCCESS;
453 }
454
455 /* }}} */
456
457 /* {{{ isConnected boolean
458 readonly=yes
459 URL: https://dom.spec.whatwg.org/#dom-node-isconnected
460 Since:
461 */
dom_node_is_connected_read(dom_object * obj,zval * retval)462 zend_result dom_node_is_connected_read(dom_object *obj, zval *retval)
463 {
464 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
465 ZVAL_BOOL(retval, php_dom_is_node_connected(nodep));
466 return SUCCESS;
467 }
468 /* }}} */
469
470 /* {{{ ownerDocument DomDocument
471 readonly=yes
472 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-node-ownerDoc
473 Since:
474 */
dom_node_owner_document_read(dom_object * obj,zval * retval)475 zend_result dom_node_owner_document_read(dom_object *obj, zval *retval)
476 {
477 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
478
479 if (nodep->type == XML_DOCUMENT_NODE || nodep->type == XML_HTML_DOCUMENT_NODE) {
480 ZVAL_NULL(retval);
481 return SUCCESS;
482 }
483
484 xmlDocPtr docp = nodep->doc;
485 if (!docp) {
486 return FAILURE;
487 }
488
489 php_dom_create_object((xmlNodePtr) docp, retval, obj);
490 return SUCCESS;
491 }
492
493 /* }}} */
494
495 /* {{{ namespaceUri string
496 readonly=yes
497 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-NodeNSname
498 Since: DOM Level 2
499 */
dom_node_namespace_uri_read(dom_object * obj,zval * retval)500 zend_result dom_node_namespace_uri_read(dom_object *obj, zval *retval)
501 {
502 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
503
504 const char *str = NULL;
505 switch (nodep->type) {
506 case XML_ELEMENT_NODE:
507 case XML_ATTRIBUTE_NODE:
508 case XML_NAMESPACE_DECL:
509 if (nodep->ns != NULL) {
510 str = (const char *) nodep->ns->href;
511 }
512 break;
513 default:
514 str = NULL;
515 break;
516 }
517
518 if (str != NULL) {
519 ZVAL_STRING(retval, str);
520 } else {
521 ZVAL_NULL(retval);
522 }
523
524 return SUCCESS;
525 }
526
527 /* }}} */
528
529 /* {{{ prefix string
530 readonly=no
531 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-NodeNSPrefix
532 Modern spec URL: https://dom.spec.whatwg.org/#concept-element-namespace-prefix
533 Since: DOM Level 2
534 */
dom_node_prefix_read(dom_object * obj,zval * retval)535 zend_result dom_node_prefix_read(dom_object *obj, zval *retval)
536 {
537 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
538
539 const char *str = NULL;
540 switch (nodep->type) {
541 case XML_ELEMENT_NODE:
542 case XML_ATTRIBUTE_NODE:
543 case XML_NAMESPACE_DECL: {
544 xmlNsPtr ns = nodep->ns;
545 if (ns != NULL && ns->prefix) {
546 str = (char *) ns->prefix;
547 }
548 break;
549 }
550 default:
551 str = NULL;
552 break;
553 }
554
555 if (str == NULL) {
556 ZVAL_EMPTY_STRING(retval);
557 } else {
558 ZVAL_STRING(retval, str);
559 }
560 return SUCCESS;
561 }
562
dom_modern_node_prefix_read(dom_object * obj,zval * retval)563 zend_result dom_modern_node_prefix_read(dom_object *obj, zval *retval)
564 {
565 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
566
567 xmlNsPtr ns = nodep->ns;
568 if (ns != NULL && ns->prefix != NULL) {
569 ZVAL_STRING(retval, (const char *) ns->prefix);
570 } else {
571 ZVAL_NULL(retval);
572 }
573 return SUCCESS;
574 }
575
dom_node_prefix_write(dom_object * obj,zval * newval)576 zend_result dom_node_prefix_write(dom_object *obj, zval *newval)
577 {
578 zend_string *prefix_str;
579 xmlNode *nsnode = NULL;
580 xmlNsPtr ns = NULL, curns;
581 char *strURI;
582 char *prefix;
583
584 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
585
586 switch (nodep->type) {
587 case XML_ELEMENT_NODE:
588 nsnode = nodep;
589 ZEND_FALLTHROUGH;
590 case XML_ATTRIBUTE_NODE:
591 if (nsnode == NULL) {
592 nsnode = nodep->parent;
593 if (nsnode == NULL) {
594 nsnode = xmlDocGetRootElement(nodep->doc);
595 }
596 }
597 /* Typed property, this is already a string */
598 ZEND_ASSERT(Z_TYPE_P(newval) == IS_STRING);
599 prefix_str = Z_STR_P(newval);
600
601 prefix = ZSTR_VAL(prefix_str);
602 if (*prefix == '\0') {
603 /* The empty string namespace prefix does not exist.
604 * We should fall back to the default namespace in this case. */
605 prefix = NULL;
606 }
607 if (nsnode && nodep->ns != NULL && !xmlStrEqual(nodep->ns->prefix, BAD_CAST prefix)) {
608 strURI = (char *) nodep->ns->href;
609 /* Validate namespace naming constraints */
610 if (strURI == NULL ||
611 (zend_string_equals_literal(prefix_str, "xml") && strcmp(strURI, (char *) XML_XML_NAMESPACE)) ||
612 (nodep->type == XML_ATTRIBUTE_NODE && zend_string_equals_literal(prefix_str, "xmlns") &&
613 strcmp(strURI, DOM_XMLNS_NS_URI)) ||
614 (nodep->type == XML_ATTRIBUTE_NODE && !strcmp((char *) nodep->name, "xmlns"))) {
615 php_dom_throw_error(NAMESPACE_ERR, dom_get_strict_error(obj->document));
616 return FAILURE;
617 } else {
618 curns = nsnode->nsDef;
619 while (curns != NULL) {
620 if (xmlStrEqual(BAD_CAST prefix, curns->prefix) && xmlStrEqual(nodep->ns->href, curns->href)) {
621 ns = curns;
622 break;
623 }
624 curns = curns->next;
625 }
626 if (ns == NULL) {
627 ns = xmlNewNs(nsnode, nodep->ns->href, BAD_CAST prefix);
628 /* Sadly, we cannot distinguish between OOM and namespace conflict.
629 * But OOM will almost never happen. */
630 if (UNEXPECTED(ns == NULL)) {
631 php_dom_throw_error(NAMESPACE_ERR, /* strict */ true);
632 return FAILURE;
633 }
634 }
635 }
636
637 xmlSetNs(nodep, ns);
638 }
639 break;
640 default:
641 break;
642 }
643
644 return SUCCESS;
645 }
646
647 /* }}} */
648
649 /* {{{ localName string
650 readonly=yes
651 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-NodeNSLocalN
652 Since: DOM Level 2
653 */
dom_node_local_name_read(dom_object * obj,zval * retval)654 zend_result dom_node_local_name_read(dom_object *obj, zval *retval)
655 {
656 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
657
658 if (nodep->type == XML_ELEMENT_NODE || nodep->type == XML_ATTRIBUTE_NODE || nodep->type == XML_NAMESPACE_DECL) {
659 ZVAL_STRING(retval, (char *) (nodep->name));
660 } else {
661 ZVAL_NULL(retval);
662 }
663
664 return SUCCESS;
665 }
666
667 /* }}} */
668
669 /* {{{ baseURI string
670 readonly=yes
671 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Node3-baseURI
672 Since: DOM Level 3
673 */
dom_node_base_uri_read(dom_object * obj,zval * retval)674 zend_result dom_node_base_uri_read(dom_object *obj, zval *retval)
675 {
676 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
677
678 xmlChar *baseuri = xmlNodeGetBase(nodep->doc, nodep);
679 if (baseuri) {
680 ZVAL_STRING(retval, (const char *) baseuri);
681 xmlFree(baseuri);
682 } else {
683 if (php_dom_follow_spec_intern(obj)) {
684 if (nodep->doc->URL) {
685 ZVAL_STRING(retval, (const char *) nodep->doc->URL);
686 } else {
687 ZVAL_STRING(retval, "about:blank");
688 }
689 } else {
690 ZVAL_NULL(retval);
691 }
692 }
693
694 return SUCCESS;
695 }
696
697 /* }}} */
698
699 /* {{{ textContent string
700 readonly=no
701 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Node3-textContent
702 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-textcontent
703 Since: DOM Level 3
704 */
705 /* Determines when the operation is a no-op. */
dom_skip_text_content(dom_object * obj,xmlNodePtr nodep)706 static bool dom_skip_text_content(dom_object *obj, xmlNodePtr nodep)
707 {
708 if (php_dom_follow_spec_intern(obj)) {
709 int type = nodep->type;
710 if (type != XML_DOCUMENT_FRAG_NODE && type != XML_ELEMENT_NODE && type != XML_ATTRIBUTE_NODE
711 && type != XML_TEXT_NODE && type != XML_CDATA_SECTION_NODE && type != XML_COMMENT_NODE && type != XML_PI_NODE) {
712 /* Yes, success... It's a no-op for these cases. */
713 return true;
714 }
715 }
716 return false;
717 }
718
dom_node_text_content_read(dom_object * obj,zval * retval)719 zend_result dom_node_text_content_read(dom_object *obj, zval *retval)
720 {
721 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
722
723 if (dom_skip_text_content(obj, nodep)) {
724 ZVAL_NULL(retval);
725 } else {
726 php_dom_get_content_into_zval(nodep, retval, false);
727 }
728
729 return SUCCESS;
730 }
731
dom_node_text_content_write(dom_object * obj,zval * newval)732 zend_result dom_node_text_content_write(dom_object *obj, zval *newval)
733 {
734 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
735
736 php_libxml_invalidate_node_list_cache(obj->document);
737
738 /* Typed property, this is already a string */
739 ZEND_ASSERT(Z_TYPE_P(newval) == IS_STRING || Z_TYPE_P(newval) == IS_NULL);
740 const xmlChar *xmlChars;
741 size_t len;
742 if (Z_TYPE_P(newval) == IS_NULL) {
743 xmlChars = (const xmlChar *) "";
744 len = 0;
745 } else {
746 xmlChars = (const xmlChar *) Z_STRVAL_P(newval);
747 len = Z_STRLEN_P(newval);
748 }
749
750 int type = nodep->type;
751
752 /* We can't directly call xmlNodeSetContent, because it might encode the string through
753 * xmlStringLenGetNodeList for types XML_DOCUMENT_FRAG_NODE, XML_ELEMENT_NODE, XML_ATTRIBUTE_NODE.
754 * See tree.c:xmlNodeSetContent in libxml.
755 * In these cases we need to use a text node to avoid the encoding.
756 * For the other cases, we *can* rely on xmlNodeSetContent because it is either a no-op, or handles
757 * the content without encoding. */
758 if (type == XML_DOCUMENT_FRAG_NODE || type == XML_ELEMENT_NODE || type == XML_ATTRIBUTE_NODE) {
759 dom_remove_all_children(nodep);
760 xmlNode *textNode = xmlNewDocTextLen(nodep->doc, xmlChars, len);
761 xmlAddChild(nodep, textNode);
762 } else {
763 xmlNodeSetContent(nodep, xmlChars);
764 }
765
766 return SUCCESS;
767 }
768
769 /* }}} */
770
_php_dom_insert_fragment(xmlNodePtr nodep,xmlNodePtr prevsib,xmlNodePtr nextsib,xmlNodePtr fragment,dom_object * intern)771 static xmlNodePtr _php_dom_insert_fragment(xmlNodePtr nodep, xmlNodePtr prevsib, xmlNodePtr nextsib, xmlNodePtr fragment, dom_object *intern) /* {{{ */
772 {
773 xmlNodePtr newchild, node;
774
775 newchild = fragment->children;
776
777 if (newchild) {
778 if (prevsib == NULL) {
779 nodep->children = newchild;
780 } else {
781 prevsib->next = newchild;
782 }
783 newchild->prev = prevsib;
784 if (nextsib == NULL) {
785 nodep->last = fragment->last;
786 } else {
787 fragment->last->next = nextsib;
788 nextsib->prev = fragment->last;
789 }
790
791 node = newchild;
792 while (node != NULL) {
793 node->parent = nodep;
794 if (node->doc != nodep->doc) {
795 xmlSetTreeDoc(node, nodep->doc);
796 dom_object *childobj = node->_private;
797 if (childobj != NULL) {
798 childobj->document = intern->document;
799 php_libxml_increment_doc_ref((php_libxml_node_object *)childobj, NULL);
800 }
801 }
802 if (node == fragment->last) {
803 break;
804 }
805 node = node->next;
806 }
807
808 fragment->children = NULL;
809 fragment->last = NULL;
810 }
811
812 return newchild;
813 }
814 /* }}} */
815
816 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-952280727
817 Since:
818 */
dom_node_insert_before_legacy(zval * return_value,zval * ref,dom_object * intern,dom_object * childobj,xmlNodePtr parentp,xmlNodePtr child)819 static void dom_node_insert_before_legacy(zval *return_value, zval *ref, dom_object *intern, dom_object *childobj, xmlNodePtr parentp, xmlNodePtr child)
820 {
821 if (!dom_node_children_valid(parentp)) {
822 RETURN_FALSE;
823 }
824
825 xmlNodePtr new_child = NULL;
826 bool stricterror = dom_get_strict_error(intern->document);
827
828 if (dom_node_is_read_only(parentp) == SUCCESS ||
829 (child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
830 php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
831 RETURN_FALSE;
832 }
833
834 if (dom_hierarchy(parentp, child) == FAILURE) {
835 php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
836 RETURN_FALSE;
837 }
838
839 if (child->doc != parentp->doc && child->doc != NULL) {
840 php_dom_throw_error(WRONG_DOCUMENT_ERR, stricterror);
841 RETURN_FALSE;
842 }
843
844 if (child->type == XML_DOCUMENT_FRAG_NODE && child->children == NULL) {
845 /* TODO Drop Warning? */
846 php_error_docref(NULL, E_WARNING, "Document Fragment is empty");
847 RETURN_FALSE;
848 }
849
850 if (child->doc == NULL && parentp->doc != NULL) {
851 childobj->document = intern->document;
852 php_libxml_increment_doc_ref((php_libxml_node_object *)childobj, NULL);
853 }
854
855 php_libxml_invalidate_node_list_cache(intern->document);
856
857 if (ref != NULL) {
858 xmlNodePtr refp;
859 dom_object *refpobj;
860 DOM_GET_OBJ(refp, ref, xmlNodePtr, refpobj);
861 if (refp->parent != parentp) {
862 php_dom_throw_error(NOT_FOUND_ERR, stricterror);
863 RETURN_FALSE;
864 }
865
866 if (child->parent != NULL) {
867 xmlUnlinkNode(child);
868 }
869
870 if (child->type == XML_TEXT_NODE && (refp->type == XML_TEXT_NODE ||
871 (refp->prev != NULL && refp->prev->type == XML_TEXT_NODE))) {
872 if (child->doc == NULL) {
873 xmlSetTreeDoc(child, parentp->doc);
874 }
875 new_child = child;
876 new_child->parent = refp->parent;
877 new_child->next = refp;
878 new_child->prev = refp->prev;
879 refp->prev = new_child;
880 if (new_child->prev != NULL) {
881 new_child->prev->next = new_child;
882 }
883 if (new_child->parent != NULL) {
884 if (new_child->parent->children == refp) {
885 new_child->parent->children = new_child;
886 }
887 }
888
889 } else if (child->type == XML_ATTRIBUTE_NODE) {
890 xmlAttrPtr lastattr;
891
892 if (child->ns == NULL)
893 lastattr = xmlHasProp(refp->parent, child->name);
894 else
895 lastattr = xmlHasNsProp(refp->parent, child->name, child->ns->href);
896 if (lastattr != NULL && lastattr->type != XML_ATTRIBUTE_DECL) {
897 if (lastattr != (xmlAttrPtr) child) {
898 xmlUnlinkNode((xmlNodePtr) lastattr);
899 php_libxml_node_free_resource((xmlNodePtr) lastattr);
900 } else {
901 DOM_RET_OBJ(child, intern);
902 return;
903 }
904 }
905 new_child = xmlAddPrevSibling(refp, child);
906 if (UNEXPECTED(NULL == new_child)) {
907 goto cannot_add;
908 }
909 } else if (child->type == XML_DOCUMENT_FRAG_NODE) {
910 xmlNodePtr last = child->last;
911 new_child = _php_dom_insert_fragment(parentp, refp->prev, refp, child, intern);
912 dom_reconcile_ns_list(parentp->doc, new_child, last);
913 } else {
914 new_child = xmlAddPrevSibling(refp, child);
915 if (UNEXPECTED(NULL == new_child)) {
916 goto cannot_add;
917 }
918 dom_reconcile_ns(parentp->doc, new_child);
919 }
920 } else {
921 if (child->parent != NULL){
922 xmlUnlinkNode(child);
923 }
924 if (child->type == XML_TEXT_NODE && parentp->last != NULL && parentp->last->type == XML_TEXT_NODE) {
925 child->parent = parentp;
926 if (child->doc == NULL) {
927 xmlSetTreeDoc(child, parentp->doc);
928 }
929 new_child = child;
930 if (parentp->children == NULL) {
931 parentp->children = child;
932 parentp->last = child;
933 } else {
934 child = parentp->last;
935 child->next = new_child;
936 new_child->prev = child;
937 parentp->last = new_child;
938 }
939 } else if (child->type == XML_ATTRIBUTE_NODE) {
940 xmlAttrPtr lastattr;
941
942 if (child->ns == NULL)
943 lastattr = xmlHasProp(parentp, child->name);
944 else
945 lastattr = xmlHasNsProp(parentp, child->name, child->ns->href);
946 if (lastattr != NULL && lastattr->type != XML_ATTRIBUTE_DECL) {
947 if (lastattr != (xmlAttrPtr) child) {
948 xmlUnlinkNode((xmlNodePtr) lastattr);
949 php_libxml_node_free_resource((xmlNodePtr) lastattr);
950 } else {
951 DOM_RET_OBJ(child, intern);
952 return;
953 }
954 }
955 new_child = xmlAddChild(parentp, child);
956 if (UNEXPECTED(NULL == new_child)) {
957 goto cannot_add;
958 }
959 } else if (child->type == XML_DOCUMENT_FRAG_NODE) {
960 xmlNodePtr last = child->last;
961 new_child = _php_dom_insert_fragment(parentp, parentp->last, NULL, child, intern);
962 dom_reconcile_ns_list(parentp->doc, new_child, last);
963 } else {
964 new_child = xmlAddChild(parentp, child);
965 if (UNEXPECTED(NULL == new_child)) {
966 goto cannot_add;
967 }
968 dom_reconcile_ns(parentp->doc, new_child);
969 }
970 }
971
972 DOM_RET_OBJ(new_child, intern);
973 return;
974 cannot_add:
975 zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode");
976 RETURN_THROWS();
977 }
978 /* }}} end dom_node_insert_before */
979
980 /* https://dom.spec.whatwg.org/#dom-node-insertbefore */
dom_node_insert_before_modern(zval * return_value,zval * ref,dom_object * intern,dom_object * childobj,xmlNodePtr parentp,xmlNodePtr child)981 static void dom_node_insert_before_modern(zval *return_value, zval *ref, dom_object *intern, dom_object *childobj, xmlNodePtr parentp, xmlNodePtr child)
982 {
983 xmlNodePtr refp = NULL;
984 dom_object *refobjp;
985 if (php_dom_pre_insert_is_parent_invalid(parentp)) {
986 php_dom_throw_error(HIERARCHY_REQUEST_ERR, /* strict */ true);
987 RETURN_THROWS();
988 }
989 if (ref != NULL) {
990 DOM_GET_OBJ(refp, ref, xmlNodePtr, refobjp);
991 }
992 php_libxml_invalidate_node_list_cache(intern->document);
993 php_dom_pre_insert(intern->document, child, parentp, refp);
994 DOM_RET_OBJ(child, intern);
995 }
996
dom_node_insert_before(INTERNAL_FUNCTION_PARAMETERS,bool modern)997 static void dom_node_insert_before(INTERNAL_FUNCTION_PARAMETERS, bool modern)
998 {
999 zval *id, *node, *ref = NULL;
1000 xmlNodePtr child, parentp;
1001 dom_object *intern, *childobj;
1002
1003 id = ZEND_THIS;
1004 if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|O!", &node, dom_get_node_ce(modern), &ref, dom_get_node_ce(modern)) == FAILURE) {
1005 RETURN_THROWS();
1006 }
1007
1008 DOM_GET_OBJ(parentp, id, xmlNodePtr, intern);
1009
1010 DOM_GET_OBJ(child, node, xmlNodePtr, childobj);
1011
1012 if (modern) {
1013 dom_node_insert_before_modern(return_value, ref, intern, childobj, parentp, child);
1014 } else {
1015 dom_node_insert_before_legacy(return_value, ref, intern, childobj, parentp, child);
1016 }
1017 }
1018
PHP_METHOD(DOMNode,insertBefore)1019 PHP_METHOD(DOMNode, insertBefore)
1020 {
1021 dom_node_insert_before(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1022 }
1023
PHP_METHOD(DOM_Node,insertBefore)1024 PHP_METHOD(DOM_Node, insertBefore)
1025 {
1026 dom_node_insert_before(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1027 }
1028
1029 /* https://dom.spec.whatwg.org/#concept-node-replace */
dom_replace_node_validity_checks(xmlNodePtr parent,xmlNodePtr node,xmlNodePtr child)1030 static zend_result dom_replace_node_validity_checks(xmlNodePtr parent, xmlNodePtr node, xmlNodePtr child)
1031 {
1032 /* 1. If parent is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException. */
1033 if (php_dom_pre_insert_is_parent_invalid(parent)) {
1034 php_dom_throw_error(HIERARCHY_REQUEST_ERR, /* strict */ true);
1035 return FAILURE;
1036 }
1037
1038 /* 2. If node is a host-including inclusive ancestor of parent, then throw a "HierarchyRequestError" DOMException. */
1039 if (dom_hierarchy(parent, node) != SUCCESS) {
1040 php_dom_throw_error(HIERARCHY_REQUEST_ERR, /* strict */ true);
1041 return FAILURE;
1042 }
1043
1044 /* 3. If child’s parent is not parent, then throw a "NotFoundError" DOMException. */
1045 if (child->parent != parent) {
1046 php_dom_throw_error(NOT_FOUND_ERR, /* strict */ true);
1047 return FAILURE;
1048 }
1049
1050 /* 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException. */
1051 if (node->type != XML_DOCUMENT_FRAG_NODE
1052 && node->type != XML_DTD_NODE
1053 && node->type != XML_ELEMENT_NODE
1054 && node->type != XML_TEXT_NODE
1055 && node->type != XML_CDATA_SECTION_NODE
1056 && node->type != XML_COMMENT_NODE
1057 && node->type != XML_PI_NODE) {
1058 php_dom_throw_error(HIERARCHY_REQUEST_ERR, /* strict */ true);
1059 return FAILURE;
1060 }
1061
1062 /* 5. If either node is a Text node and parent is a document, or node is a doctype and parent is not a document,
1063 * then throw a "HierarchyRequestError" DOMException. */
1064 bool parent_is_document = parent->type == XML_DOCUMENT_NODE || parent->type == XML_HTML_DOCUMENT_NODE;
1065 if (parent_is_document && (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE)) {
1066 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot insert text as a child of a document", /* strict */ true);
1067 return FAILURE;
1068 }
1069 if (!parent_is_document && node->type == XML_DTD_NODE) {
1070 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot insert a document type into anything other than a document", /* strict */ true);
1071 return FAILURE;
1072 }
1073
1074 /* 6. If parent is a document, and any of the statements below, switched on the interface node implements, are true,
1075 * then throw a "HierarchyRequestError" DOMException.
1076 * Spec note: These statements _slightly_ differ from the pre-insert algorithm. */
1077 if (parent_is_document) {
1078 /* DocumentFragment */
1079 if (node->type == XML_DOCUMENT_FRAG_NODE) {
1080 if (!php_dom_fragment_insertion_hierarchy_check_replace(parent, node, child)) {
1081 return FAILURE;
1082 }
1083 }
1084 /* Element */
1085 else if (node->type == XML_ELEMENT_NODE) {
1086 /* parent has an element child that is not child ... */
1087 if (xmlDocGetRootElement((xmlDocPtr) parent) != child) {
1088 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot have more than one element child in a document", /* strict */ true);
1089 return FAILURE;
1090 }
1091 /* ... or a doctype is following child. */
1092 if (php_dom_has_sibling_following_node(child, XML_DTD_NODE)) {
1093 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Document types must be the first child in a document", /* strict */ true);
1094 return FAILURE;
1095 }
1096 }
1097 /* DocumentType */
1098 else if (node->type == XML_DTD_NODE) {
1099 /* parent has a doctype child that is not child, or an element is preceding child. */
1100 xmlDocPtr doc = (xmlDocPtr) parent;
1101 if (doc->intSubset != (xmlDtdPtr) child || php_dom_has_sibling_preceding_node(child, XML_ELEMENT_NODE)) {
1102 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Document types must be the first child in a document", /* strict */ true);
1103 return FAILURE;
1104 }
1105 }
1106 }
1107
1108 /* Steps 7 and onward perform the removal and insertion, and also track changes for mutation records.
1109 * We don't implement mutation records so we can just skip straight to the replace part. */
1110
1111 return SUCCESS;
1112 }
1113
1114 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-785887307
1115 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-replacechild
1116 Since:
1117 */
dom_node_replace_child(INTERNAL_FUNCTION_PARAMETERS,bool modern)1118 static void dom_node_replace_child(INTERNAL_FUNCTION_PARAMETERS, bool modern)
1119 {
1120 zval *id, *newnode, *oldnode;
1121 xmlNodePtr newchild, oldchild, nodep;
1122 dom_object *intern, *newchildobj, *oldchildobj;
1123
1124 id = ZEND_THIS;
1125 if (zend_parse_parameters(ZEND_NUM_ARGS(), "OO", &newnode, dom_get_node_ce(modern), &oldnode, dom_get_node_ce(modern)) == FAILURE) {
1126 RETURN_THROWS();
1127 }
1128
1129 DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1130
1131 DOM_GET_OBJ(newchild, newnode, xmlNodePtr, newchildobj);
1132 DOM_GET_OBJ(oldchild, oldnode, xmlNodePtr, oldchildobj);
1133
1134 bool stricterror = dom_get_strict_error(intern->document);
1135
1136 if (newchild->doc != nodep->doc && newchild->doc != NULL) {
1137 php_dom_throw_error(WRONG_DOCUMENT_ERR, stricterror);
1138 RETURN_FALSE;
1139 }
1140
1141 if (modern) {
1142 if (dom_replace_node_validity_checks(nodep, newchild, oldchild) != SUCCESS) {
1143 RETURN_THROWS();
1144 }
1145 } else {
1146 if (!dom_node_children_valid(nodep)) {
1147 RETURN_FALSE;
1148 }
1149
1150 if (!nodep->children) {
1151 RETURN_FALSE;
1152 }
1153
1154 if (dom_node_is_read_only(nodep) == SUCCESS ||
1155 (newchild->parent != NULL && dom_node_is_read_only(newchild->parent) == SUCCESS)) {
1156 php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
1157 RETURN_FALSE;
1158 }
1159
1160 if (dom_hierarchy(nodep, newchild) == FAILURE) {
1161 php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
1162 RETURN_FALSE;
1163 }
1164
1165 if (oldchild->parent != nodep) {
1166 php_dom_throw_error(NOT_FOUND_ERR, stricterror);
1167 RETURN_FALSE;
1168 }
1169 }
1170
1171 if (newchild->type == XML_DOCUMENT_FRAG_NODE) {
1172 xmlNodePtr prevsib, nextsib;
1173 prevsib = oldchild->prev;
1174 nextsib = oldchild->next;
1175
1176 xmlUnlinkNode(oldchild);
1177
1178 xmlNodePtr last = newchild->last;
1179 newchild = _php_dom_insert_fragment(nodep, prevsib, nextsib, newchild, intern);
1180 if (newchild && !modern) {
1181 dom_reconcile_ns_list(nodep->doc, newchild, last);
1182 }
1183 } else if (oldchild != newchild) {
1184 xmlDtdPtr intSubset = xmlGetIntSubset(nodep->doc);
1185 bool replacedoctype = (intSubset == (xmlDtd *) oldchild);
1186
1187 if (newchild->doc == NULL && nodep->doc != NULL) {
1188 xmlSetTreeDoc(newchild, nodep->doc);
1189 newchildobj->document = intern->document;
1190 php_libxml_increment_doc_ref((php_libxml_node_object *)newchildobj, NULL);
1191 }
1192 xmlReplaceNode(oldchild, newchild);
1193 if (!modern) {
1194 dom_reconcile_ns(nodep->doc, newchild);
1195 }
1196
1197 if (replacedoctype) {
1198 nodep->doc->intSubset = (xmlDtd *) newchild;
1199 }
1200 }
1201 php_libxml_invalidate_node_list_cache(intern->document);
1202 DOM_RET_OBJ(oldchild, intern);
1203 }
1204
PHP_METHOD(DOMNode,replaceChild)1205 PHP_METHOD(DOMNode, replaceChild)
1206 {
1207 dom_node_replace_child(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1208 }
1209
PHP_METHOD(DOM_Node,replaceChild)1210 PHP_METHOD(DOM_Node, replaceChild)
1211 {
1212 dom_node_replace_child(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1213 }
1214 /* }}} end dom_node_replace_child */
1215
1216 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1734834066
1217 Since:
1218 */
dom_node_remove_child(INTERNAL_FUNCTION_PARAMETERS,zend_class_entry * node_ce)1219 static void dom_node_remove_child(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *node_ce)
1220 {
1221 zval *id, *node;
1222 xmlNodePtr child, nodep;
1223 dom_object *intern, *childobj;
1224
1225 id = ZEND_THIS;
1226 if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node, node_ce) == FAILURE) {
1227 RETURN_THROWS();
1228 }
1229
1230 DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1231
1232 if (!dom_node_children_valid(nodep)) {
1233 RETURN_FALSE;
1234 }
1235
1236 DOM_GET_OBJ(child, node, xmlNodePtr, childobj);
1237
1238 bool stricterror = dom_get_strict_error(intern->document);
1239
1240 if (dom_node_is_read_only(nodep) == SUCCESS ||
1241 (child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
1242 php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
1243 RETURN_FALSE;
1244 }
1245
1246 if (!nodep->children || child->parent != nodep) {
1247 php_dom_throw_error(NOT_FOUND_ERR, stricterror);
1248 RETURN_FALSE;
1249 }
1250
1251 xmlUnlinkNode(child);
1252 php_libxml_invalidate_node_list_cache(intern->document);
1253 DOM_RET_OBJ(child, intern);
1254 }
1255
PHP_METHOD(DOMNode,removeChild)1256 PHP_METHOD(DOMNode, removeChild)
1257 {
1258 dom_node_remove_child(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_node_class_entry);
1259 }
1260
PHP_METHOD(DOM_Node,removeChild)1261 PHP_METHOD(DOM_Node, removeChild)
1262 {
1263 dom_node_remove_child(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_modern_node_class_entry);
1264 }
1265 /* }}} end dom_node_remove_child */
1266
1267 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-184E7107
1268 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-appendchild
1269 Since:
1270 */
dom_node_append_child_legacy(zval * return_value,dom_object * intern,dom_object * childobj,xmlNodePtr nodep,xmlNodePtr child)1271 static void dom_node_append_child_legacy(zval *return_value, dom_object *intern, dom_object *childobj, xmlNodePtr nodep, xmlNodePtr child)
1272 {
1273 xmlNodePtr new_child = NULL;
1274
1275 if (!dom_node_children_valid(nodep)) {
1276 RETURN_FALSE;
1277 }
1278
1279 bool stricterror = dom_get_strict_error(intern->document);
1280
1281 if (dom_node_is_read_only(nodep) == SUCCESS ||
1282 (child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
1283 php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
1284 RETURN_FALSE;
1285 }
1286
1287 if (dom_hierarchy(nodep, child) == FAILURE) {
1288 php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
1289 RETURN_FALSE;
1290 }
1291
1292 if (!(child->doc == NULL || child->doc == nodep->doc)) {
1293 php_dom_throw_error(WRONG_DOCUMENT_ERR, stricterror);
1294 RETURN_FALSE;
1295 }
1296
1297 if (child->type == XML_DOCUMENT_FRAG_NODE && child->children == NULL) {
1298 /* TODO Drop Warning? */
1299 php_error_docref(NULL, E_WARNING, "Document Fragment is empty");
1300 RETURN_FALSE;
1301 }
1302
1303 if (child->doc == NULL && nodep->doc != NULL) {
1304 childobj->document = intern->document;
1305 php_libxml_increment_doc_ref((php_libxml_node_object *)childobj, NULL);
1306 }
1307
1308 if (child->parent != NULL){
1309 xmlUnlinkNode(child);
1310 }
1311
1312 if (child->type == XML_TEXT_NODE && nodep->last != NULL && nodep->last->type == XML_TEXT_NODE) {
1313 child->parent = nodep;
1314 if (child->doc == NULL) {
1315 xmlSetTreeDoc(child, nodep->doc);
1316 }
1317 new_child = child;
1318 if (nodep->children == NULL) {
1319 nodep->children = child;
1320 nodep->last = child;
1321 } else {
1322 child = nodep->last;
1323 child->next = new_child;
1324 new_child->prev = child;
1325 nodep->last = new_child;
1326 }
1327 } else if (child->type == XML_ATTRIBUTE_NODE) {
1328 xmlAttrPtr lastattr;
1329
1330 if (child->ns == NULL)
1331 lastattr = xmlHasProp(nodep, child->name);
1332 else
1333 lastattr = xmlHasNsProp(nodep, child->name, child->ns->href);
1334 if (lastattr != NULL && lastattr->type != XML_ATTRIBUTE_DECL) {
1335 if (lastattr != (xmlAttrPtr) child) {
1336 xmlUnlinkNode((xmlNodePtr) lastattr);
1337 php_libxml_node_free_resource((xmlNodePtr) lastattr);
1338 }
1339 }
1340 new_child = xmlAddChild(nodep, child);
1341 if (UNEXPECTED(new_child == NULL)) {
1342 goto cannot_add;
1343 }
1344 php_dom_reconcile_attribute_namespace_after_insertion((xmlAttrPtr) new_child);
1345 } else if (child->type == XML_DOCUMENT_FRAG_NODE) {
1346 xmlNodePtr last = child->last;
1347 new_child = _php_dom_insert_fragment(nodep, nodep->last, NULL, child, intern);
1348 dom_reconcile_ns_list(nodep->doc, new_child, last);
1349 } else if (child->type == XML_DTD_NODE) {
1350 if (nodep->doc->intSubset != NULL) {
1351 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "A document may only contain one document type", stricterror);
1352 RETURN_FALSE;
1353 }
1354 new_child = xmlAddChild(nodep, child);
1355 if (UNEXPECTED(new_child == NULL)) {
1356 goto cannot_add;
1357 }
1358 nodep->doc->intSubset = (xmlDtdPtr) new_child;
1359 } else {
1360 new_child = xmlAddChild(nodep, child);
1361 if (UNEXPECTED(new_child == NULL)) {
1362 goto cannot_add;
1363 }
1364 dom_reconcile_ns(nodep->doc, new_child);
1365 }
1366
1367 php_libxml_invalidate_node_list_cache(intern->document);
1368
1369 DOM_RET_OBJ(new_child, intern);
1370 return;
1371 cannot_add:
1372 php_dom_throw_error(INVALID_STATE_ERR, stricterror);
1373 RETURN_FALSE;
1374 }
1375 /* }}} end dom_node_append_child */
1376
PHP_METHOD(DOMNode,appendChild)1377 PHP_METHOD(DOMNode, appendChild)
1378 {
1379 zval *node;
1380 xmlNodePtr nodep, child;
1381 dom_object *intern, *childobj;
1382
1383 ZEND_PARSE_PARAMETERS_START(1, 1)
1384 Z_PARAM_OBJECT_OF_CLASS(node, dom_node_class_entry)
1385 ZEND_PARSE_PARAMETERS_END();
1386
1387 DOM_GET_OBJ(nodep, ZEND_THIS, xmlNodePtr, intern);
1388 DOM_GET_OBJ(child, node, xmlNodePtr, childobj);
1389
1390 dom_node_append_child_legacy(return_value, intern, childobj, nodep, child);
1391 }
1392
PHP_METHOD(DOM_Node,appendChild)1393 PHP_METHOD(DOM_Node, appendChild)
1394 {
1395 zval *node;
1396 xmlNodePtr nodep, child;
1397 dom_object *intern, *childobj;
1398
1399 ZEND_PARSE_PARAMETERS_START(1, 1)
1400 Z_PARAM_OBJECT_OF_CLASS(node, dom_modern_node_class_entry)
1401 ZEND_PARSE_PARAMETERS_END();
1402
1403 DOM_GET_OBJ(nodep, ZEND_THIS, xmlNodePtr, intern);
1404 DOM_GET_OBJ(child, node, xmlNodePtr, childobj);
1405
1406 /* Parent check from pre-insertion validation done here:
1407 * If parent is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException. */
1408 if (php_dom_pre_insert_is_parent_invalid(nodep)) {
1409 php_dom_throw_error(HIERARCHY_REQUEST_ERR, /* strict */ true);
1410 RETURN_THROWS();
1411 }
1412 /* Append, this doesn't do the parent check so we do it here. */
1413 php_libxml_invalidate_node_list_cache(intern->document);
1414 php_dom_node_append(intern->document, child, nodep);
1415 DOM_RET_OBJ(child, intern);
1416 }
1417
1418 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-810594187
1419 Since:
1420 */
PHP_METHOD(DOMNode,hasChildNodes)1421 PHP_METHOD(DOMNode, hasChildNodes)
1422 {
1423 zval *id;
1424 xmlNode *nodep;
1425 dom_object *intern;
1426
1427 id = ZEND_THIS;
1428 if (zend_parse_parameters_none() == FAILURE) {
1429 RETURN_THROWS();
1430 }
1431
1432 DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1433
1434 if (!dom_node_children_valid(nodep)) {
1435 RETURN_FALSE;
1436 }
1437
1438 if (nodep->children) {
1439 RETURN_TRUE;
1440 } else {
1441 RETURN_FALSE;
1442 }
1443 }
1444 /* }}} end dom_node_has_child_nodes */
1445
1446 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-3A0ED0A4
1447 Since:
1448 */
PHP_METHOD(DOMNode,cloneNode)1449 PHP_METHOD(DOMNode, cloneNode)
1450 {
1451 zval *id;
1452 xmlNode *n, *node;
1453 dom_object *intern;
1454 bool recursive = 0;
1455
1456 id = ZEND_THIS;
1457 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &recursive) == FAILURE) {
1458 RETURN_THROWS();
1459 }
1460
1461 DOM_GET_OBJ(n, id, xmlNodePtr, intern);
1462
1463 php_dom_libxml_ns_mapper *ns_mapper = NULL;
1464 bool clone_document = n->type == XML_DOCUMENT_NODE || n->type == XML_HTML_DOCUMENT_NODE;
1465 if (php_dom_follow_spec_intern(intern)) {
1466 if (clone_document) {
1467 ns_mapper = php_dom_libxml_ns_mapper_create();
1468 } else {
1469 ns_mapper = php_dom_get_ns_mapper(intern);
1470 }
1471 }
1472
1473 node = dom_clone_node(ns_mapper, n, n->doc, recursive);
1474
1475 if (!node) {
1476 if (clone_document && ns_mapper != NULL) {
1477 php_dom_libxml_ns_mapper_destroy(ns_mapper);
1478 }
1479 RETURN_FALSE;
1480 }
1481
1482 /* If document cloned we want a new document proxy */
1483 if (clone_document) {
1484 dom_object *new_intern;
1485 if (ns_mapper) {
1486 /* We have the issue here that we can't create a modern node without an intern.
1487 * Fortunately, it's impossible to have a custom document class for the modern DOM (final base class),
1488 * so we can solve this by invoking the instantiation helper directly. */
1489 zend_class_entry *ce = n->type == XML_DOCUMENT_NODE ? dom_xml_document_class_entry : dom_html_document_class_entry;
1490 new_intern = php_dom_instantiate_object_helper(return_value, ce, node, NULL);
1491 } else {
1492 DOM_RET_OBJ(node, NULL);
1493 new_intern = Z_DOMOBJ_P(return_value);
1494 }
1495 php_dom_update_document_after_clone(intern, n, new_intern, node);
1496 ZEND_ASSERT(new_intern->document->private_data == NULL);
1497 new_intern->document->private_data = php_dom_libxml_ns_mapper_header(ns_mapper);
1498 } else {
1499 if (node->type == XML_ATTRIBUTE_NODE && n->ns != NULL && node->ns == NULL) {
1500 /* Let reconciliation deal with this. The lifetime of the namespace poses no problem
1501 * because we're increasing the refcount of the document proxy at the return.
1502 * libxml2 doesn't set the ns because it can't know that this is safe. */
1503 node->ns = n->ns;
1504 }
1505
1506 DOM_RET_OBJ(node, intern);
1507 }
1508 }
1509 /* }}} end dom_node_clone_node */
1510
1511 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-normalize
1512 Since:
1513 */
PHP_METHOD(DOMNode,normalize)1514 PHP_METHOD(DOMNode, normalize)
1515 {
1516 zval *id;
1517 xmlNode *nodep;
1518 dom_object *intern;
1519
1520 id = ZEND_THIS;
1521 if (zend_parse_parameters_none() == FAILURE) {
1522 RETURN_THROWS();
1523 }
1524
1525 DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1526
1527 if (php_dom_follow_spec_intern(intern)) {
1528 php_dom_normalize_modern(nodep);
1529 } else {
1530 php_dom_normalize_legacy(nodep);
1531 }
1532 }
1533 /* }}} end dom_node_normalize */
1534
1535 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Level-2-Core-Node-supports
1536 Since: DOM Level 2
1537 */
PHP_METHOD(DOMNode,isSupported)1538 PHP_METHOD(DOMNode, isSupported)
1539 {
1540 zend_string *feature, *version;
1541
1542 if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &feature, &version) == FAILURE) {
1543 RETURN_THROWS();
1544 }
1545
1546 RETURN_BOOL(dom_has_feature(feature, version));
1547 }
1548 /* }}} end dom_node_is_supported */
1549
1550 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-NodeHasAttrs
1551 Since: DOM Level 2
1552 */
PHP_METHOD(DOMNode,hasAttributes)1553 PHP_METHOD(DOMNode, hasAttributes)
1554 {
1555 zval *id;
1556 xmlNode *nodep;
1557 dom_object *intern;
1558
1559 id = ZEND_THIS;
1560 if (zend_parse_parameters_none() == FAILURE) {
1561 RETURN_THROWS();
1562 }
1563
1564 DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1565
1566 if (nodep->type != XML_ELEMENT_NODE)
1567 RETURN_FALSE;
1568
1569 if (nodep->properties) {
1570 RETURN_TRUE;
1571 } else {
1572 RETURN_FALSE;
1573 }
1574 }
1575 /* }}} end dom_node_has_attributes */
1576
1577 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Node3-isSameNode
1578 Since: DOM Level 3
1579 */
dom_node_is_same_node(INTERNAL_FUNCTION_PARAMETERS,zval * node)1580 static void dom_node_is_same_node(INTERNAL_FUNCTION_PARAMETERS, zval *node)
1581 {
1582 zval *id;
1583 xmlNodePtr nodeotherp, nodep;
1584 dom_object *intern, *nodeotherobj;
1585
1586 DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
1587
1588 DOM_GET_OBJ(nodeotherp, node, xmlNodePtr, nodeotherobj);
1589
1590 if (nodep == nodeotherp) {
1591 RETURN_TRUE;
1592 } else {
1593 RETURN_FALSE;
1594 }
1595 }
1596
PHP_METHOD(DOMNode,isSameNode)1597 PHP_METHOD(DOMNode, isSameNode)
1598 {
1599 zval *node;
1600 if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node, dom_node_class_entry) != SUCCESS) {
1601 RETURN_THROWS();
1602 }
1603
1604 dom_node_is_same_node(INTERNAL_FUNCTION_PARAM_PASSTHRU, node);
1605 }
1606
PHP_METHOD(DOM_Node,isSameNode)1607 PHP_METHOD(DOM_Node, isSameNode)
1608 {
1609 zval *node;
1610 if (zend_parse_parameters(ZEND_NUM_ARGS(), "O!", &node, dom_modern_node_class_entry) != SUCCESS) {
1611 RETURN_THROWS();
1612 }
1613
1614 if (node == NULL) {
1615 RETURN_FALSE;
1616 }
1617
1618 dom_node_is_same_node(INTERNAL_FUNCTION_PARAM_PASSTHRU, node);
1619 }
1620 /* }}} end dom_node_is_same_node */
1621
php_dom_node_is_content_equal(const xmlNode * this,const xmlNode * other)1622 static bool php_dom_node_is_content_equal(const xmlNode *this, const xmlNode *other)
1623 {
1624 xmlChar *this_content = xmlNodeGetContent(this);
1625 xmlChar *other_content = xmlNodeGetContent(other);
1626 bool result = xmlStrEqual(this_content, other_content);
1627 xmlFree(this_content);
1628 xmlFree(other_content);
1629 return result;
1630 }
1631
php_dom_node_is_ns_uri_equal(const xmlNode * this,const xmlNode * other)1632 static bool php_dom_node_is_ns_uri_equal(const xmlNode *this, const xmlNode *other)
1633 {
1634 const xmlChar *this_ns = this->ns ? this->ns->href : NULL;
1635 const xmlChar *other_ns = other->ns ? other->ns->href : NULL;
1636 return xmlStrEqual(this_ns, other_ns);
1637 }
1638
php_dom_node_is_ns_prefix_equal(const xmlNode * this,const xmlNode * other)1639 static bool php_dom_node_is_ns_prefix_equal(const xmlNode *this, const xmlNode *other)
1640 {
1641 const xmlChar *this_ns = this->ns ? this->ns->prefix : NULL;
1642 const xmlChar *other_ns = other->ns ? other->ns->prefix : NULL;
1643 return xmlStrEqual(this_ns, other_ns);
1644 }
1645
1646 static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other, bool spec_compliant);
1647
1648 #define PHP_DOM_FUNC_CAT(prefix, suffix) prefix##_##suffix
1649 /* xmlNode and xmlNs have incompatible struct layouts, i.e. the next field is in a different offset */
1650 #define PHP_DOM_DEFINE_LIST_COUNTER_HELPER(type) \
1651 static size_t PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(const type *node) \
1652 { \
1653 size_t counter = 0; \
1654 while (node) { \
1655 counter++; \
1656 node = node->next; \
1657 } \
1658 return counter; \
1659 }
1660 #define PHP_DOM_DEFINE_LIST_EQUALITY_ORDERED_HELPER(type) \
1661 static bool PHP_DOM_FUNC_CAT(php_dom_node_list_equality_check_ordered, type)(const type *list1, const type *list2, bool spec_compliant) \
1662 { \
1663 size_t count = PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list1); \
1664 if (count != PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list2)) { \
1665 return false; \
1666 } \
1667 for (size_t i = 0; i < count; i++) { \
1668 if (!php_dom_node_is_equal_node((const xmlNode *) list1, (const xmlNode *) list2, spec_compliant)) { \
1669 return false; \
1670 } \
1671 list1 = list1->next; \
1672 list2 = list2->next; \
1673 } \
1674 return true; \
1675 }
1676 #define PHP_DOM_DEFINE_LIST_EQUALITY_UNORDERED_HELPER(type) \
1677 static bool PHP_DOM_FUNC_CAT(php_dom_node_list_equality_check_unordered, type)(const type *list1, const type *list2, bool spec_compliant)\
1678 { \
1679 size_t count = PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list1); \
1680 if (count != PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list2)) { \
1681 return false; \
1682 } \
1683 for (const type *n1 = list1; n1 != NULL; n1 = n1->next) { \
1684 bool found = false; \
1685 for (const type *n2 = list2; n2 != NULL && !found; n2 = n2->next) { \
1686 if (php_dom_node_is_equal_node((const xmlNode *) n1, (const xmlNode *) n2, spec_compliant)) { \
1687 found = true; \
1688 } \
1689 } \
1690 if (!found) { \
1691 return false; \
1692 } \
1693 } \
1694 return true; \
1695 }
1696
1697 PHP_DOM_DEFINE_LIST_COUNTER_HELPER(xmlNode)
PHP_DOM_DEFINE_LIST_COUNTER_HELPER(xmlNs)1698 PHP_DOM_DEFINE_LIST_COUNTER_HELPER(xmlNs)
1699 PHP_DOM_DEFINE_LIST_EQUALITY_ORDERED_HELPER(xmlNode)
1700 PHP_DOM_DEFINE_LIST_EQUALITY_UNORDERED_HELPER(xmlNode)
1701 PHP_DOM_DEFINE_LIST_EQUALITY_UNORDERED_HELPER(xmlNs)
1702
1703 static bool php_dom_is_equal_attr(const xmlAttr *this_attr, const xmlAttr *other_attr)
1704 {
1705 ZEND_ASSERT(this_attr != NULL);
1706 ZEND_ASSERT(other_attr != NULL);
1707 return xmlStrEqual(this_attr->name, other_attr->name)
1708 && php_dom_node_is_ns_uri_equal((const xmlNode *) this_attr, (const xmlNode *) other_attr)
1709 && php_dom_node_is_content_equal((const xmlNode *) this_attr, (const xmlNode *) other_attr);
1710 }
1711
php_dom_node_is_equal_node(const xmlNode * this,const xmlNode * other,bool spec_compliant)1712 static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other, bool spec_compliant)
1713 {
1714 ZEND_ASSERT(this != NULL);
1715 ZEND_ASSERT(other != NULL);
1716
1717 if (this->type != other->type) {
1718 return false;
1719 }
1720
1721 /* Notes:
1722 * - XML_DOCUMENT_TYPE_NODE is no longer created by libxml2, we only have to support XML_DTD_NODE.
1723 * - element and attribute declarations are not exposed as nodes in DOM, so no comparison is needed for those. */
1724 if (this->type == XML_ELEMENT_NODE) {
1725 return xmlStrEqual(this->name, other->name)
1726 && php_dom_node_is_ns_prefix_equal(this, other)
1727 && php_dom_node_is_ns_uri_equal(this, other)
1728 /* Check attributes first, then namespace declarations, then children */
1729 && php_dom_node_list_equality_check_unordered_xmlNode((const xmlNode *) this->properties, (const xmlNode *) other->properties, spec_compliant)
1730 && (spec_compliant || php_dom_node_list_equality_check_unordered_xmlNs(this->nsDef, other->nsDef, false))
1731 && php_dom_node_list_equality_check_ordered_xmlNode(this->children, other->children, spec_compliant);
1732 } else if (this->type == XML_DTD_NODE) {
1733 /* Note: in the living spec entity declarations and notations are no longer compared because they're considered obsolete. */
1734 const xmlDtd *this_dtd = (const xmlDtd *) this;
1735 const xmlDtd *other_dtd = (const xmlDtd *) other;
1736 return xmlStrEqual(this_dtd->name, other_dtd->name)
1737 && xmlStrEqual(this_dtd->ExternalID, other_dtd->ExternalID)
1738 && xmlStrEqual(this_dtd->SystemID, other_dtd->SystemID);
1739 } else if (this->type == XML_PI_NODE) {
1740 return xmlStrEqual(this->name, other->name) && xmlStrEqual(this->content, other->content);
1741 } else if (this->type == XML_TEXT_NODE || this->type == XML_COMMENT_NODE || this->type == XML_CDATA_SECTION_NODE) {
1742 return xmlStrEqual(this->content, other->content);
1743 } else if (this->type == XML_ATTRIBUTE_NODE) {
1744 const xmlAttr *this_attr = (const xmlAttr *) this;
1745 const xmlAttr *other_attr = (const xmlAttr *) other;
1746 return php_dom_is_equal_attr(this_attr, other_attr);
1747 } else if (this->type == XML_ENTITY_REF_NODE) {
1748 return xmlStrEqual(this->name, other->name);
1749 } else if (this->type == XML_ENTITY_DECL || this->type == XML_NOTATION_NODE || this->type == XML_ENTITY_NODE) {
1750 const xmlEntity *this_entity = (const xmlEntity *) this;
1751 const xmlEntity *other_entity = (const xmlEntity *) other;
1752 return this_entity->etype == other_entity->etype
1753 && xmlStrEqual(this_entity->name, other_entity->name)
1754 && xmlStrEqual(this_entity->ExternalID, other_entity->ExternalID)
1755 && xmlStrEqual(this_entity->SystemID, other_entity->SystemID)
1756 && php_dom_node_is_content_equal(this, other);
1757 } else if (this->type == XML_NAMESPACE_DECL) {
1758 const xmlNs *this_ns = (const xmlNs *) this;
1759 const xmlNs *other_ns = (const xmlNs *) other;
1760 return xmlStrEqual(this_ns->prefix, other_ns->prefix) && xmlStrEqual(this_ns->href, other_ns->href);
1761 } else if (this->type == XML_DOCUMENT_FRAG_NODE || this->type == XML_HTML_DOCUMENT_NODE || this->type == XML_DOCUMENT_NODE) {
1762 return php_dom_node_list_equality_check_ordered_xmlNode(this->children, other->children, spec_compliant);
1763 }
1764
1765 return false;
1766 }
1767
1768 /* {{{ URL: https://dom.spec.whatwg.org/#dom-node-isequalnode (for everything still in the living spec)
1769 * URL: https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/DOM3-Core.html#core-Node3-isEqualNode (for old nodes removed from the living spec)
1770 Since: DOM Level 3
1771 */
dom_node_is_equal_node_common(INTERNAL_FUNCTION_PARAMETERS,bool modern)1772 static void dom_node_is_equal_node_common(INTERNAL_FUNCTION_PARAMETERS, bool modern)
1773 {
1774 zval *id, *node;
1775 xmlNodePtr otherp, nodep;
1776 dom_object *intern;
1777
1778 id = ZEND_THIS;
1779 if (zend_parse_parameters(ZEND_NUM_ARGS(), "O!", &node, dom_get_node_ce(modern)) == FAILURE) {
1780 RETURN_THROWS();
1781 }
1782
1783 if (node == NULL) {
1784 RETURN_FALSE;
1785 }
1786
1787 DOM_GET_OBJ(otherp, node, xmlNodePtr, intern);
1788 DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
1789
1790 if (nodep == otherp) {
1791 RETURN_TRUE;
1792 }
1793
1794 /* Empty fragments/documents only match if they're both empty */
1795 if (nodep == NULL || otherp == NULL) {
1796 RETURN_BOOL(nodep == NULL && otherp == NULL);
1797 }
1798
1799 RETURN_BOOL(php_dom_node_is_equal_node(nodep, otherp, modern));
1800 }
1801
PHP_METHOD(DOMNode,isEqualNode)1802 PHP_METHOD(DOMNode, isEqualNode)
1803 {
1804 dom_node_is_equal_node_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1805 }
1806
PHP_METHOD(DOM_Node,isEqualNode)1807 PHP_METHOD(DOM_Node, isEqualNode)
1808 {
1809 dom_node_is_equal_node_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1810 }
1811 /* }}} end DOMNode::isEqualNode */
1812
1813 /* https://dom.spec.whatwg.org/#locate-a-namespace-prefix */
dom_locate_a_namespace_prefix(xmlNodePtr elem,const char * uri)1814 static const xmlChar *dom_locate_a_namespace_prefix(xmlNodePtr elem, const char *uri)
1815 {
1816 do {
1817 /* 1. If element’s namespace is namespace and its namespace prefix is non-null, then return its namespace prefix. */
1818 if (elem->ns != NULL && elem->ns->prefix != NULL && xmlStrEqual(elem->ns->href, BAD_CAST uri)) {
1819 return elem->ns->prefix;
1820 }
1821
1822 /* 2. If element has an attribute whose namespace prefix is "xmlns" and value is namespace,
1823 * then return element’s first such attribute’s local name. */
1824 for (xmlAttrPtr attr = elem->properties; attr != NULL; attr = attr->next) {
1825 if (attr->ns != NULL && attr->children != NULL
1826 && xmlStrEqual(attr->ns->prefix, BAD_CAST "xmlns") && xmlStrEqual(attr->children->content, BAD_CAST uri)) {
1827 return attr->name;
1828 }
1829 }
1830
1831 /* 3. If element’s parent element is not null, then return the result of running locate a namespace prefix on that element using namespace. */
1832 elem = elem->parent;
1833 } while (elem != NULL && elem->type == XML_ELEMENT_NODE);
1834
1835 /* 4. Return null. */
1836 return NULL;
1837 }
1838
1839 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Node3-lookupNamespacePrefix
1840 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-lookupprefix
1841 Since: DOM Level 3
1842 */
dom_node_lookup_prefix(INTERNAL_FUNCTION_PARAMETERS,bool modern)1843 static void dom_node_lookup_prefix(INTERNAL_FUNCTION_PARAMETERS, bool modern)
1844 {
1845 zval *id;
1846 xmlNodePtr nodep, lookupp = NULL;
1847 dom_object *intern;
1848 xmlNsPtr nsptr;
1849 size_t uri_len = 0;
1850 char *uri;
1851
1852 id = ZEND_THIS;
1853 if (zend_parse_parameters(ZEND_NUM_ARGS(), modern ? "s!" : "s", &uri, &uri_len) == FAILURE) {
1854 RETURN_THROWS();
1855 }
1856
1857 DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
1858
1859 /* 1. If namespace is null or the empty string, then return null. */
1860 if (uri_len > 0) {
1861 /* 2. Switch on the interface this implements: */
1862 switch (nodep->type) {
1863 case XML_ELEMENT_NODE:
1864 lookupp = nodep;
1865 break;
1866 case XML_DOCUMENT_NODE:
1867 case XML_HTML_DOCUMENT_NODE:
1868 lookupp = xmlDocGetRootElement((xmlDocPtr) nodep);
1869 break;
1870 case XML_ENTITY_NODE :
1871 case XML_NOTATION_NODE:
1872 case XML_DOCUMENT_FRAG_NODE:
1873 case XML_DOCUMENT_TYPE_NODE:
1874 case XML_DTD_NODE:
1875 RETURN_NULL();
1876 break;
1877 default:
1878 lookupp = nodep->parent;
1879 }
1880
1881 if (lookupp != NULL) {
1882 if (modern) {
1883 const char * result = (const char *) dom_locate_a_namespace_prefix(lookupp, uri);
1884 if (result != NULL) {
1885 RETURN_STRING(result);
1886 }
1887 } else {
1888 nsptr = xmlSearchNsByHref(lookupp->doc, lookupp, BAD_CAST uri);
1889 if (nsptr && nsptr->prefix != NULL) {
1890 RETURN_STRING((const char *) nsptr->prefix);
1891 }
1892 }
1893 }
1894 }
1895
1896 RETURN_NULL();
1897 }
1898
PHP_METHOD(DOMNode,lookupPrefix)1899 PHP_METHOD(DOMNode, lookupPrefix)
1900 {
1901 dom_node_lookup_prefix(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1902 }
1903
PHP_METHOD(DOM_Node,lookupPrefix)1904 PHP_METHOD(DOM_Node, lookupPrefix)
1905 {
1906 dom_node_lookup_prefix(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1907 }
1908 /* }}} end dom_node_lookup_prefix */
1909
1910 /* https://dom.spec.whatwg.org/#locate-a-namespace */
dom_locate_a_namespace(xmlNodePtr node,const zend_string * prefix)1911 static const char *dom_locate_a_namespace(xmlNodePtr node, const zend_string *prefix)
1912 {
1913 /* switch on the interface node implements: */
1914 if (node->type == XML_ELEMENT_NODE) {
1915 if (prefix != NULL) {
1916 /* 1. If prefix is "xml", then return the XML namespace. */
1917 if (zend_string_equals_literal_ci(prefix, "xml")) {
1918 return DOM_XML_NS_URI;
1919 }
1920
1921 /* 2. If prefix is "xmlns", then return the XMLNS namespace. */
1922 if (zend_string_equals_literal_ci(prefix, "xmlns")) {
1923 return DOM_XMLNS_NS_URI;
1924 }
1925 }
1926
1927 do {
1928 /* 3. If its namespace is non-null and its namespace prefix is prefix, then return namespace. */
1929 if (node->ns != NULL && xmlStrEqual(node->ns->prefix, BAD_CAST (prefix ? ZSTR_VAL(prefix) : NULL))) {
1930 return (const char *) node->ns->href;
1931 }
1932
1933 /* 4. If it has an attribute whose namespace is the XMLNS namespace, namespace prefix is "xmlns", and local name is prefix,
1934 * or if prefix is null and it has an attribute whose namespace is the XMLNS namespace, namespace prefix is null, and local name is "xmlns",
1935 * then return its value if it is not the empty string, and null otherwise. */
1936 for (xmlAttrPtr attr = node->properties; attr != NULL; attr = attr->next) {
1937 if (attr->ns == NULL || !php_dom_ns_is_fast_ex(attr->ns, php_dom_ns_is_xmlns_magic_token)) {
1938 continue;
1939 }
1940 if ((prefix != NULL && xmlStrEqual(attr->ns->prefix, BAD_CAST "xmlns") && xmlStrEqual(attr->name, BAD_CAST ZSTR_VAL(prefix)))
1941 || (prefix == NULL && attr->ns->prefix == NULL && xmlStrEqual(attr->name, BAD_CAST "xmlns"))) {
1942 if (attr->children != NULL && attr->children->content[0] != '\0') {
1943 return (const char *) attr->children->content;
1944 } else {
1945 return NULL;
1946 }
1947 }
1948 }
1949
1950 /* 5. If its parent element is null, then return null. */
1951 if (node->parent == NULL || node->parent->type != XML_ELEMENT_NODE) {
1952 return NULL;
1953 }
1954
1955 /* 6. Return the result of running locate a namespace on its parent element using prefix. */
1956 node = node->parent;
1957 } while (true);
1958 } else if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) {
1959 /* 1. If its document element is null, then return null. */
1960 node = xmlDocGetRootElement((xmlDocPtr) node);
1961 if (UNEXPECTED(node == NULL)) {
1962 return NULL;
1963 }
1964
1965 /* 2. Return the result of running locate a namespace on its document element using prefix. */
1966 return dom_locate_a_namespace(node, prefix);
1967 } else if (node->type == XML_DTD_NODE || node->type == XML_DOCUMENT_FRAG_NODE) {
1968 return NULL;
1969 } else {
1970 /* 1. If its element is null, then return null / If its parent element is null, then return null. */
1971 if (node->parent == NULL || node->parent->type != XML_ELEMENT_NODE) {
1972 return NULL;
1973 }
1974
1975 /* 2. Return the result of running locate a namespace on its element using prefix. */
1976 return dom_locate_a_namespace(node->parent, prefix);
1977 }
1978 }
1979
1980 /* {{{ URL: http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-isDefaultNamespace
1981 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-isdefaultnamespace
1982 Since: DOM Level 3
1983 */
PHP_METHOD(DOMNode,isDefaultNamespace)1984 PHP_METHOD(DOMNode, isDefaultNamespace)
1985 {
1986 zval *id;
1987 xmlNodePtr nodep;
1988 dom_object *intern;
1989 xmlNsPtr nsptr;
1990 size_t uri_len = 0;
1991 char *uri;
1992
1993 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &uri, &uri_len) != SUCCESS) {
1994 RETURN_THROWS();
1995 }
1996
1997 DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
1998
1999 if (uri_len > 0) {
2000 if (nodep->type == XML_DOCUMENT_NODE || nodep->type == XML_HTML_DOCUMENT_NODE) {
2001 nodep = xmlDocGetRootElement((xmlDocPtr) nodep);
2002 if (nodep == NULL) {
2003 RETURN_FALSE;
2004 }
2005 }
2006
2007 nsptr = xmlSearchNs(nodep->doc, nodep, NULL);
2008 if (nsptr && xmlStrEqual(nsptr->href, BAD_CAST uri)) {
2009 RETURN_TRUE;
2010 }
2011 }
2012
2013 RETURN_FALSE;
2014 }
2015
PHP_METHOD(DOM_Node,isDefaultNamespace)2016 PHP_METHOD(DOM_Node, isDefaultNamespace)
2017 {
2018 zval *id;
2019 xmlNodePtr nodep;
2020 dom_object *intern;
2021 size_t uri_len = 0;
2022 char *uri;
2023
2024 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!", &uri, &uri_len) != SUCCESS) {
2025 RETURN_THROWS();
2026 }
2027
2028 DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
2029
2030 if (uri_len == 0) {
2031 uri = NULL;
2032 }
2033 const char *ns_uri = dom_locate_a_namespace(nodep, NULL);
2034 RETURN_BOOL(xmlStrEqual(BAD_CAST uri, BAD_CAST ns_uri));
2035 }
2036 /* }}} end dom_node_is_default_namespace */
2037
2038 /* {{{ URL: http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI
2039 Modern spec URL: https://dom.spec.whatwg.org/#dom-node-lookupnamespaceuri
2040 Since: DOM Level 3
2041 */
PHP_METHOD(DOMNode,lookupNamespaceURI)2042 PHP_METHOD(DOMNode, lookupNamespaceURI)
2043 {
2044 zval *id;
2045 xmlNodePtr nodep;
2046 dom_object *intern;
2047 xmlNsPtr nsptr;
2048 zend_string *prefix;
2049
2050 id = ZEND_THIS;
2051 if (zend_parse_parameters(ZEND_NUM_ARGS(), "S!", &prefix) == FAILURE) {
2052 RETURN_THROWS();
2053 }
2054
2055 DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
2056
2057 if (php_dom_follow_spec_intern(intern)) {
2058 if (prefix != NULL && ZSTR_LEN(prefix) == 0) {
2059 prefix = NULL;
2060 }
2061 const char *ns_uri = dom_locate_a_namespace(nodep, prefix);
2062 if (ns_uri == NULL) {
2063 RETURN_NULL();
2064 } else {
2065 RETURN_STRING(ns_uri);
2066 }
2067 } else {
2068 if (nodep->type == XML_DOCUMENT_NODE || nodep->type == XML_HTML_DOCUMENT_NODE) {
2069 nodep = xmlDocGetRootElement((xmlDocPtr) nodep);
2070 if (nodep == NULL) {
2071 RETURN_NULL();
2072 }
2073 }
2074
2075 nsptr = xmlSearchNs(nodep->doc, nodep, BAD_CAST (prefix ? ZSTR_VAL(prefix) : NULL));
2076 if (nsptr && nsptr->href != NULL) {
2077 RETURN_STRING((char *) nsptr->href);
2078 }
2079 }
2080
2081 RETURN_NULL();
2082 }
2083 /* }}} end dom_node_lookup_namespace_uri */
2084
dom_canonicalize_node_parent_lookup_cb(void * user_data,xmlNodePtr node,xmlNodePtr parent)2085 static int dom_canonicalize_node_parent_lookup_cb(void *user_data, xmlNodePtr node, xmlNodePtr parent)
2086 {
2087 xmlNodePtr root = user_data;
2088 /* We have to unroll the first iteration because node->parent
2089 * is not necessarily equal to parent due to libxml2 tree rules (ns decls out of the tree for example). */
2090 if (node == root) {
2091 return 1;
2092 }
2093 node = parent;
2094 while (node != NULL) {
2095 if (node == root) {
2096 return 1;
2097 }
2098 node = node->parent;
2099 }
2100
2101 return 0;
2102 }
2103
dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS,int mode)2104 static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */
2105 {
2106 zval *id;
2107 zval *xpath_array=NULL, *ns_prefixes=NULL;
2108 xmlNodePtr nodep;
2109 xmlDocPtr docp;
2110 xmlNodeSetPtr nodeset = NULL;
2111 dom_object *intern;
2112 bool exclusive=0, with_comments=0;
2113 xmlChar **inclusive_ns_prefixes = NULL;
2114 char *file = NULL;
2115 int ret = -1;
2116 size_t file_len = 0;
2117 xmlOutputBufferPtr buf;
2118 xmlXPathContextPtr ctxp=NULL;
2119 xmlXPathObjectPtr xpathobjp=NULL;
2120
2121 id = ZEND_THIS;
2122 if (mode == 0) {
2123 if (zend_parse_parameters(ZEND_NUM_ARGS(),
2124 "|bba!a!", &exclusive, &with_comments,
2125 &xpath_array, &ns_prefixes) == FAILURE) {
2126 RETURN_THROWS();
2127 }
2128 } else {
2129 if (zend_parse_parameters(ZEND_NUM_ARGS(),
2130 "s|bba!a!", &file, &file_len, &exclusive,
2131 &with_comments, &xpath_array, &ns_prefixes) == FAILURE) {
2132 RETURN_THROWS();
2133 }
2134 }
2135
2136 DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
2137
2138 docp = nodep->doc;
2139
2140 if (! docp) {
2141 zend_throw_error(NULL, "Node must be associated with a document");
2142 RETURN_THROWS();
2143 }
2144
2145 bool simple_node_parent_lookup_callback = false;
2146 if (xpath_array == NULL) {
2147 /* Optimization: if the node is a document, all nodes may be included, no extra filtering or nodeset necessary. */
2148 if (nodep->type != XML_DOCUMENT_NODE && nodep->type != XML_HTML_DOCUMENT_NODE) {
2149 simple_node_parent_lookup_callback = true;
2150 }
2151 } else {
2152 /*xpath query from xpath_array */
2153 HashTable *ht = Z_ARRVAL_P(xpath_array);
2154 zval *tmp;
2155 char *xquery;
2156
2157 /* Find "query" key */
2158 tmp = zend_hash_find(ht, ZSTR_KNOWN(ZEND_STR_QUERY));
2159 if (!tmp) {
2160 /* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */
2161 zend_argument_value_error(3 + mode, "must have a \"query\" key");
2162 RETURN_THROWS();
2163 }
2164 if (Z_TYPE_P(tmp) != IS_STRING) {
2165 /* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */
2166 zend_argument_type_error(3 + mode, "\"query\" option must be a string, %s given", zend_zval_value_name(tmp));
2167 RETURN_THROWS();
2168 }
2169 xquery = Z_STRVAL_P(tmp);
2170
2171 ctxp = xmlXPathNewContext(docp);
2172 ctxp->node = nodep;
2173
2174 tmp = zend_hash_str_find(ht, "namespaces", sizeof("namespaces")-1);
2175 if (tmp && Z_TYPE_P(tmp) == IS_ARRAY && !HT_IS_PACKED(Z_ARRVAL_P(tmp))) {
2176 zval *tmpns;
2177 zend_string *prefix;
2178
2179 ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(tmp), prefix, tmpns) {
2180 if (Z_TYPE_P(tmpns) == IS_STRING) {
2181 if (prefix) {
2182 xmlXPathRegisterNs(ctxp, BAD_CAST ZSTR_VAL(prefix), BAD_CAST Z_STRVAL_P(tmpns));
2183 }
2184 }
2185 } ZEND_HASH_FOREACH_END();
2186 }
2187
2188 xpathobjp = xmlXPathEvalExpression(BAD_CAST xquery, ctxp);
2189 ctxp->node = NULL;
2190 if (xpathobjp && xpathobjp->type == XPATH_NODESET) {
2191 nodeset = xpathobjp->nodesetval;
2192 } else {
2193 if (xpathobjp) {
2194 xmlXPathFreeObject(xpathobjp);
2195 }
2196 xmlXPathFreeContext(ctxp);
2197 zend_throw_error(NULL, "XPath query did not return a nodeset");
2198 RETURN_THROWS();
2199 }
2200 }
2201
2202 if (ns_prefixes != NULL) {
2203 if (exclusive) {
2204 zval *tmpns;
2205 int nscount = 0;
2206
2207 inclusive_ns_prefixes = safe_emalloc(zend_hash_num_elements(Z_ARRVAL_P(ns_prefixes)) + 1,
2208 sizeof(xmlChar *), 0);
2209 ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(ns_prefixes), tmpns) {
2210 if (Z_TYPE_P(tmpns) == IS_STRING) {
2211 inclusive_ns_prefixes[nscount++] = BAD_CAST Z_STRVAL_P(tmpns);
2212 }
2213 } ZEND_HASH_FOREACH_END();
2214 inclusive_ns_prefixes[nscount] = NULL;
2215 } else {
2216 php_error_docref(NULL, E_NOTICE,
2217 "Inclusive namespace prefixes only allowed in exclusive mode.");
2218 }
2219 }
2220
2221 if (mode == 1) {
2222 buf = xmlOutputBufferCreateFilename(file, NULL, 0);
2223 } else {
2224 buf = xmlAllocOutputBuffer(NULL);
2225 }
2226
2227 if (buf != NULL) {
2228 if (simple_node_parent_lookup_callback) {
2229 ret = xmlC14NExecute(docp, dom_canonicalize_node_parent_lookup_cb, nodep, exclusive, inclusive_ns_prefixes, with_comments, buf);
2230 } else {
2231 ret = xmlC14NDocSaveTo(docp, nodeset, exclusive, inclusive_ns_prefixes, with_comments, buf);
2232 }
2233 }
2234
2235 if (inclusive_ns_prefixes != NULL) {
2236 efree(inclusive_ns_prefixes);
2237 }
2238 if (xpathobjp != NULL) {
2239 xmlXPathFreeObject(xpathobjp);
2240 }
2241 if (ctxp != NULL) {
2242 xmlXPathFreeContext(ctxp);
2243 }
2244
2245 if (buf == NULL || ret < 0) {
2246 RETVAL_FALSE;
2247 } else {
2248 if (mode == 0) {
2249 ret = xmlOutputBufferGetSize(buf);
2250 if (ret > 0) {
2251 RETVAL_STRINGL((char *) xmlOutputBufferGetContent(buf), ret);
2252 } else {
2253 RETVAL_EMPTY_STRING();
2254 }
2255 }
2256 }
2257
2258 if (buf) {
2259 int bytes;
2260
2261 bytes = xmlOutputBufferClose(buf);
2262 if (mode == 1 && (ret >= 0)) {
2263 RETURN_LONG(bytes);
2264 }
2265 }
2266 }
2267 /* }}} */
2268
2269 /* {{{ Canonicalize nodes to a string */
PHP_METHOD(DOMNode,C14N)2270 PHP_METHOD(DOMNode, C14N)
2271 {
2272 dom_canonicalization(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
2273 }
2274 /* }}} */
2275
2276 /* {{{ Canonicalize nodes to a file */
PHP_METHOD(DOMNode,C14NFile)2277 PHP_METHOD(DOMNode, C14NFile)
2278 {
2279 dom_canonicalization(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
2280 }
2281 /* }}} */
2282
2283 /* {{{ Gets an xpath for a node */
dom_node_get_node_path(INTERNAL_FUNCTION_PARAMETERS,bool throw)2284 static void dom_node_get_node_path(INTERNAL_FUNCTION_PARAMETERS, bool throw)
2285 {
2286 zval *id;
2287 xmlNode *nodep;
2288 dom_object *intern;
2289 char *value;
2290
2291 if (zend_parse_parameters_none() == FAILURE) {
2292 RETURN_THROWS();
2293 }
2294
2295 DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
2296
2297 value = (char *) xmlGetNodePath(nodep);
2298 if (value == NULL) {
2299 /* This is only possible when an invalid argument is passed (e.g. namespace declaration, but that's not the case for this call site),
2300 * or on allocation failure. So in other words, this only happens on allocation failure. */
2301 if (throw) {
2302 php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
2303 RETURN_THROWS();
2304 }
2305 RETURN_NULL();
2306 } else {
2307 RETVAL_STRING(value);
2308 xmlFree(value);
2309 }
2310 }
2311
PHP_METHOD(DOMNode,getNodePath)2312 PHP_METHOD(DOMNode, getNodePath)
2313 {
2314 dom_node_get_node_path(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
2315 }
2316
PHP_METHOD(DOM_Node,getNodePath)2317 PHP_METHOD(DOM_Node, getNodePath)
2318 {
2319 dom_node_get_node_path(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
2320 }
2321 /* }}} */
2322
2323 /* {{{ Gets line number for a node */
PHP_METHOD(DOMNode,getLineNo)2324 PHP_METHOD(DOMNode, getLineNo)
2325 {
2326 zval *id;
2327 xmlNode *nodep;
2328 dom_object *intern;
2329
2330 if (zend_parse_parameters_none() == FAILURE) {
2331 RETURN_THROWS();
2332 }
2333
2334 DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
2335
2336 RETURN_LONG(xmlGetLineNo(nodep));
2337 }
2338 /* }}} */
2339
2340 /* {{{ URL: https://dom.spec.whatwg.org/#dom-node-contains
2341 Since:
2342 */
dom_node_contains(xmlNodePtr thisp,xmlNodePtr otherp)2343 static bool dom_node_contains(xmlNodePtr thisp, xmlNodePtr otherp)
2344 {
2345 do {
2346 if (otherp == thisp) {
2347 return true;
2348 }
2349 otherp = otherp->parent;
2350 } while (otherp);
2351
2352 return false;
2353 }
2354
PHP_METHOD(DOMNode,contains)2355 PHP_METHOD(DOMNode, contains)
2356 {
2357 zval *other, *id;
2358 xmlNodePtr otherp, thisp;
2359 dom_object *unused_intern;
2360
2361 ZEND_PARSE_PARAMETERS_START(1, 1)
2362 Z_PARAM_OBJECT_OR_NULL(other)
2363 ZEND_PARSE_PARAMETERS_END();
2364
2365 if (other == NULL) {
2366 RETURN_FALSE;
2367 }
2368
2369 if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(other), dom_node_class_entry) && !instanceof_function(Z_OBJCE_P(other), dom_namespace_node_class_entry))) {
2370 zend_argument_type_error(1, "must be of type DOMNode|DOMNameSpaceNode|null, %s given", zend_zval_value_name(other));
2371 RETURN_THROWS();
2372 }
2373
2374 DOM_GET_OBJ(otherp, other, xmlNodePtr, unused_intern);
2375 DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, unused_intern);
2376
2377 RETURN_BOOL(dom_node_contains(thisp, otherp));
2378 }
2379
PHP_METHOD(DOM_Node,contains)2380 PHP_METHOD(DOM_Node, contains)
2381 {
2382 zval *other, *id;
2383 xmlNodePtr otherp, thisp;
2384 dom_object *unused_intern;
2385
2386 ZEND_PARSE_PARAMETERS_START(1, 1)
2387 Z_PARAM_OBJECT_OF_CLASS_OR_NULL(other, dom_modern_node_class_entry)
2388 ZEND_PARSE_PARAMETERS_END();
2389
2390 if (other == NULL) {
2391 RETURN_FALSE;
2392 }
2393
2394 DOM_GET_OBJ(otherp, other, xmlNodePtr, unused_intern);
2395 DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, unused_intern);
2396
2397 RETURN_BOOL(dom_node_contains(thisp, otherp));
2398 }
2399 /* }}} */
2400
2401 /* {{{ URL: https://dom.spec.whatwg.org/#dom-node-getrootnode
2402 Since:
2403 */
PHP_METHOD(DOMNode,getRootNode)2404 PHP_METHOD(DOMNode, getRootNode)
2405 {
2406 zval *id;
2407 xmlNodePtr thisp;
2408 dom_object *intern;
2409 /* Unused now because we don't support the shadow DOM nodes. Options only influence shadow DOM nodes. */
2410 zval *options;
2411
2412 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a!", &options) != SUCCESS) {
2413 RETURN_THROWS();
2414 }
2415
2416 DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, intern);
2417
2418 while (thisp->parent) {
2419 thisp = thisp->parent;
2420 }
2421
2422 DOM_RET_OBJ(thisp, intern);
2423 }
2424 /* }}} */
2425
2426 /* {{{ URL: https://dom.spec.whatwg.org/#dom-node-comparedocumentposition (last check date 2023-07-24)
2427 Since:
2428 */
2429
2430 #define DOCUMENT_POSITION_DISCONNECTED 0x01
2431 #define DOCUMENT_POSITION_PRECEDING 0x02
2432 #define DOCUMENT_POSITION_FOLLOWING 0x04
2433 #define DOCUMENT_POSITION_CONTAINS 0x08
2434 #define DOCUMENT_POSITION_CONTAINED_BY 0x10
2435 #define DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC 0x20
2436
dom_node_compare_document_position(INTERNAL_FUNCTION_PARAMETERS,zend_class_entry * node_ce)2437 static void dom_node_compare_document_position(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *node_ce)
2438 {
2439 zval *id, *node_zval;
2440 xmlNodePtr other, this;
2441 dom_object *this_intern, *other_intern;
2442
2443 if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node_zval, node_ce) != SUCCESS) {
2444 RETURN_THROWS();
2445 }
2446
2447 DOM_GET_THIS_OBJ(this, id, xmlNodePtr, this_intern);
2448 DOM_GET_OBJ(other, node_zval, xmlNodePtr, other_intern);
2449
2450 /* Step 1 */
2451 if (this == other) {
2452 RETURN_LONG(0);
2453 }
2454
2455 /* Step 2 */
2456 xmlNodePtr node1 = other;
2457 xmlNodePtr node2 = this;
2458
2459 /* Step 3 */
2460 xmlNodePtr attr1 = NULL;
2461 xmlNodePtr attr2 = NULL;
2462
2463 /* Step 4 */
2464 if (node1->type == XML_ATTRIBUTE_NODE) {
2465 attr1 = node1;
2466 node1 = attr1->parent;
2467 }
2468
2469 /* Step 5 */
2470 if (node2->type == XML_ATTRIBUTE_NODE) {
2471 /* 5.1 */
2472 attr2 = node2;
2473 node2 = attr2->parent;
2474
2475 /* 5.2 */
2476 if (attr1 != NULL && node1 != NULL && node2 == node1) {
2477 for (const xmlAttr *attr = node2->properties; attr != NULL; attr = attr->next) {
2478 if (php_dom_is_equal_attr(attr, (const xmlAttr *) attr1)) {
2479 RETURN_LONG(DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | DOCUMENT_POSITION_PRECEDING);
2480 } else if (php_dom_is_equal_attr(attr, (const xmlAttr *) attr2)) {
2481 RETURN_LONG(DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | DOCUMENT_POSITION_FOLLOWING);
2482 }
2483 }
2484 }
2485 }
2486
2487 /* Step 6 */
2488 /* We first check the first condition,
2489 * and as we need the root later anyway we'll cache the root and perform the root check after this if. */
2490 if (node1 == NULL || node2 == NULL) {
2491 goto disconnected;
2492 }
2493 bool node2_is_ancestor_of_node1 = false;
2494 size_t node1_depth = 0;
2495 xmlNodePtr node1_root = node1;
2496 while (node1_root->parent) {
2497 node1_root = node1_root->parent;
2498 if (node1_root == node2) {
2499 node2_is_ancestor_of_node1 = true;
2500 }
2501 node1_depth++;
2502 }
2503 bool node1_is_ancestor_of_node2 = false;
2504 size_t node2_depth = 0;
2505 xmlNodePtr node2_root = node2;
2506 while (node2_root->parent) {
2507 node2_root = node2_root->parent;
2508 if (node2_root == node1) {
2509 node1_is_ancestor_of_node2 = true;
2510 }
2511 node2_depth++;
2512 }
2513 /* Second condition from step 6 */
2514 if (node1_root != node2_root) {
2515 goto disconnected;
2516 }
2517
2518 /* Step 7 */
2519 if ((node1_is_ancestor_of_node2 && attr1 == NULL) || (node1 == node2 && attr2 != NULL)) {
2520 RETURN_LONG(DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING);
2521 }
2522
2523 /* Step 8 */
2524 if ((node2_is_ancestor_of_node1 && attr2 == NULL) || (node1 == node2 && attr1 != NULL)) {
2525 RETURN_LONG(DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING);
2526 }
2527
2528 /* Special case: comparing children and attributes.
2529 * They belong to a different tree and are therefore hard to compare, but spec demands attributes to precede children
2530 * according to the pre-order depth-first search ordering.
2531 * Because their tree is different, the node parents only meet at the common element instead of earlier.
2532 * Therefore, it seems that one is the ancestor of the other. */
2533 if (node1_is_ancestor_of_node2) {
2534 ZEND_ASSERT(attr1 != NULL); /* Would've been handled in step 7 otherwise */
2535 RETURN_LONG(DOCUMENT_POSITION_PRECEDING);
2536 } else if (node2_is_ancestor_of_node1) {
2537 ZEND_ASSERT(attr2 != NULL); /* Would've been handled in step 8 otherwise */
2538 RETURN_LONG(DOCUMENT_POSITION_FOLLOWING);
2539 }
2540
2541 /* Step 9 */
2542
2543 /* We'll use the following strategy (which was already prepared during step 6) to implement this efficiently:
2544 * 1. Move nodes upwards such that they are at the same depth.
2545 * 2. Then we move both nodes upwards simultaneously until their parents are equal.
2546 * 3. If we then move node1 to the next entry repeatedly and we encounter node2,
2547 * then we know node1 precedes node2. Otherwise, node2 must precede node1. */
2548 /* 1. */
2549 if (node1_depth > node2_depth) {
2550 do {
2551 node1 = node1->parent;
2552 node1_depth--;
2553 } while (node1_depth > node2_depth);
2554 } else if (node2_depth > node1_depth) {
2555 do {
2556 node2 = node2->parent;
2557 node2_depth--;
2558 } while (node2_depth > node1_depth);
2559 }
2560 /* 2. */
2561 while (node1->parent != node2->parent) {
2562 node1 = node1->parent;
2563 node2 = node2->parent;
2564 }
2565 /* 3. */
2566 ZEND_ASSERT(node1 != node2);
2567 ZEND_ASSERT(node1 != NULL);
2568 ZEND_ASSERT(node2 != NULL);
2569 do {
2570 node1 = node1->next;
2571 if (node1 == node2) {
2572 RETURN_LONG(DOCUMENT_POSITION_PRECEDING);
2573 }
2574 } while (node1 != NULL);
2575
2576 /* Step 10 */
2577 RETURN_LONG(DOCUMENT_POSITION_FOLLOWING);
2578
2579 disconnected:;
2580 zend_long ordering;
2581 if (node1 == node2) {
2582 /* Degenerate case, they're both NULL, but the ordering must be consistent... */
2583 ZEND_ASSERT(node1 == NULL);
2584 ordering = other_intern < this_intern ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING;
2585 } else {
2586 ordering = node1 < node2 ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING;
2587 }
2588 RETURN_LONG(DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | ordering);
2589 }
2590
PHP_METHOD(DOMNode,compareDocumentPosition)2591 PHP_METHOD(DOMNode, compareDocumentPosition)
2592 {
2593 dom_node_compare_document_position(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_node_class_entry);
2594 }
2595
PHP_METHOD(DOM_Node,compareDocumentPosition)2596 PHP_METHOD(DOM_Node, compareDocumentPosition)
2597 {
2598 dom_node_compare_document_position(INTERNAL_FUNCTION_PARAM_PASSTHRU, dom_modern_node_class_entry);
2599 }
2600 /* }}} */
2601
2602 /**
2603 * We want to block the serialization and unserialization of DOM classes.
2604 * However, using @not-serializable makes the child classes also not serializable, even if the user implements the methods.
2605 * So instead, we implement the methods wherein we throw exceptions.
2606 * The reason we choose these methods is because:
2607 * - If the user implements __serialize / __unserialize, the respective throwing methods are not called.
2608 * - If the user implements __sleep / __wakeup, then it's also not a problem because they will not enter the throwing methods.
2609 */
2610
PHP_METHOD(DOM_Node,__construct)2611 PHP_METHOD(DOM_Node, __construct)
2612 {
2613 ZEND_UNREACHABLE();
2614 }
2615
PHP_METHOD(DOMNode,__sleep)2616 PHP_METHOD(DOMNode, __sleep)
2617 {
2618 if (zend_parse_parameters_none() != SUCCESS) {
2619 RETURN_THROWS();
2620 }
2621
2622 zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed, unless serialization methods are implemented in a subclass", ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
2623 RETURN_THROWS();
2624 }
2625
PHP_METHOD(DOMNode,__wakeup)2626 PHP_METHOD(DOMNode, __wakeup)
2627 {
2628 if (zend_parse_parameters_none() != SUCCESS) {
2629 RETURN_THROWS();
2630 }
2631
2632 zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed, unless unserialization methods are implemented in a subclass", ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
2633 RETURN_THROWS();
2634 }
2635
2636 #endif
2637