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