1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | https://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Benjamin Eberlei <beberlei@php.net> |
16 | Niels Dossche <nielsdos@php.net> |
17 +----------------------------------------------------------------------+
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "php.h"
25 #if defined(HAVE_LIBXML) && defined(HAVE_DOM)
26 #include "php_dom.h"
27 #include "internal_helpers.h"
28 #include "dom_properties.h"
29
30 /* {{{ firstElementChild DomParentNode
31 readonly=yes
32 URL: https://www.w3.org/TR/dom/#dom-parentnode-firstelementchild
33 */
dom_parent_node_first_element_child_read(dom_object * obj,zval * retval)34 zend_result dom_parent_node_first_element_child_read(dom_object *obj, zval *retval)
35 {
36 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
37
38 xmlNodePtr first = NULL;
39 if (dom_node_children_valid(nodep)) {
40 first = nodep->children;
41
42 while (first && first->type != XML_ELEMENT_NODE) {
43 first = first->next;
44 }
45 }
46
47 if (!first) {
48 ZVAL_NULL(retval);
49 return SUCCESS;
50 }
51
52 php_dom_create_object(first, retval, obj);
53 return SUCCESS;
54 }
55 /* }}} */
56
57 /* {{{ lastElementChild DomParentNode
58 readonly=yes
59 URL: https://www.w3.org/TR/dom/#dom-parentnode-lastelementchild
60 */
dom_parent_node_last_element_child_read(dom_object * obj,zval * retval)61 zend_result dom_parent_node_last_element_child_read(dom_object *obj, zval *retval)
62 {
63 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
64
65 xmlNodePtr last = NULL;
66 if (dom_node_children_valid(nodep)) {
67 last = nodep->last;
68
69 while (last && last->type != XML_ELEMENT_NODE) {
70 last = last->prev;
71 }
72 }
73
74 if (!last) {
75 ZVAL_NULL(retval);
76 return SUCCESS;
77 }
78
79 php_dom_create_object(last, retval, obj);
80 return SUCCESS;
81 }
82 /* }}} */
83
84 /* {{{ childElementCount DomParentNode
85 readonly=yes
86 https://www.w3.org/TR/dom/#dom-parentnode-childelementcount
87 */
dom_parent_node_child_element_count(dom_object * obj,zval * retval)88 zend_result dom_parent_node_child_element_count(dom_object *obj, zval *retval)
89 {
90 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
91
92 zend_long count = 0;
93 if (dom_node_children_valid(nodep)) {
94 xmlNodePtr first = nodep->children;
95
96 while (first != NULL) {
97 if (first->type == XML_ELEMENT_NODE) {
98 count++;
99 }
100
101 first = first->next;
102 }
103 }
104
105 ZVAL_LONG(retval, count);
106
107 return SUCCESS;
108 }
109 /* }}} */
110
dom_is_node_in_list(const zval * nodes,uint32_t nodesc,const xmlNode * node_to_find)111 static bool dom_is_node_in_list(const zval *nodes, uint32_t nodesc, const xmlNode *node_to_find)
112 {
113 for (uint32_t i = 0; i < nodesc; i++) {
114 if (Z_TYPE(nodes[i]) == IS_OBJECT) {
115 if (dom_object_get_node(Z_DOMOBJ_P(nodes + i)) == node_to_find) {
116 return true;
117 }
118 }
119 }
120
121 return false;
122 }
123
dom_doc_from_context_node(xmlNodePtr contextNode)124 static xmlDocPtr dom_doc_from_context_node(xmlNodePtr contextNode)
125 {
126 if (contextNode->type == XML_DOCUMENT_NODE || contextNode->type == XML_HTML_DOCUMENT_NODE) {
127 return (xmlDocPtr) contextNode;
128 } else {
129 return contextNode->doc;
130 }
131 }
132
133 /* Citing from the docs (https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/libxml2-tree.html#xmlAddChild):
134 * "Add a new node to @parent, at the end of the child (or property) list merging adjacent TEXT nodes (in which case @cur is freed)".
135 * So we must use a custom way of adding that does not merge. */
dom_add_child_without_merging(xmlNodePtr parent,xmlNodePtr child)136 static void dom_add_child_without_merging(xmlNodePtr parent, xmlNodePtr child)
137 {
138 if (parent->children == NULL) {
139 parent->children = child;
140 } else {
141 xmlNodePtr last = parent->last;
142 last->next = child;
143 child->prev = last;
144 }
145 parent->last = child;
146 child->parent = parent;
147 }
148
dom_fragment_assign_parent_node(xmlNodePtr parentNode,xmlNodePtr fragment)149 static void dom_fragment_assign_parent_node(xmlNodePtr parentNode, xmlNodePtr fragment)
150 {
151 xmlNodePtr node = fragment->children;
152
153 while (node != NULL) {
154 node->parent = parentNode;
155
156 if (node == fragment->last) {
157 break;
158 }
159 node = node->next;
160 }
161 }
162
163 /* This part is common logic between the pre-insertion validity and replaceChild code. */
dom_fragment_common_hierarchy_check_part(xmlNodePtr node,bool * seen_element)164 static bool dom_fragment_common_hierarchy_check_part(xmlNodePtr node, bool *seen_element)
165 {
166 /* If node has more than one element child or has a Text node child. */
167 xmlNodePtr iter = node->children;
168 *seen_element = false;
169 while (iter != NULL) {
170 if (iter->type == XML_ELEMENT_NODE) {
171 if (*seen_element) {
172 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot have more than one element child in a document", /* strict */ true);
173 return false;
174 }
175 *seen_element = true;
176 } else if (iter->type == XML_TEXT_NODE || iter->type == XML_CDATA_SECTION_NODE) {
177 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot insert text as a child of a document", /* strict */ true);
178 return false;
179 }
180 iter = iter->next;
181 }
182 return true;
183 }
184
185 /* https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
186 * DocumentFragment validation part. */
php_dom_fragment_insertion_hierarchy_check_pre_insertion(xmlNodePtr parent,xmlNodePtr node,xmlNodePtr child)187 bool php_dom_fragment_insertion_hierarchy_check_pre_insertion(xmlNodePtr parent, xmlNodePtr node, xmlNodePtr child)
188 {
189 bool seen_element;
190 if (!dom_fragment_common_hierarchy_check_part(node, &seen_element)) {
191 return false;
192 }
193
194 /* Otherwise, if node has one element child
195 * and either parent has an element child, child is a doctype, or child is non-null and a doctype is following child. */
196 if (seen_element) {
197 if (php_dom_has_child_of_type(parent, XML_ELEMENT_NODE)) {
198 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot have more than one element child in a document", /* strict */ true);
199 return false;
200 }
201
202 if (child != NULL && (child->type == XML_DTD_NODE || php_dom_has_sibling_following_node(child, XML_DTD_NODE))) {
203 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Document types must be the first child in a document", /* strict */ true);
204 return false;
205 }
206 }
207
208 return true;
209 }
210
211 /* https://dom.spec.whatwg.org/#concept-node-replace
212 * DocumentFragment validation part. */
php_dom_fragment_insertion_hierarchy_check_replace(xmlNodePtr parent,xmlNodePtr node,xmlNodePtr child)213 bool php_dom_fragment_insertion_hierarchy_check_replace(xmlNodePtr parent, xmlNodePtr node, xmlNodePtr child)
214 {
215 bool seen_element;
216 if (!dom_fragment_common_hierarchy_check_part(node, &seen_element)) {
217 return false;
218 }
219
220 /* Otherwise, if node has one element child
221 * and either parent has an element child that is not child or a doctype is following child. */
222 if (seen_element) {
223 xmlNodePtr iter = parent->children;
224 while (iter != NULL) {
225 if (iter->type == XML_ELEMENT_NODE && iter != child) {
226 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot have more than one element child in a document", /* strict */ true);
227 return false;
228 }
229 iter = iter->next;
230 }
231
232 ZEND_ASSERT(child != NULL);
233 if (php_dom_has_sibling_following_node(child, XML_DTD_NODE)) {
234 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Document types must be the first child in a document", /* strict */ true);
235 return false;
236 }
237 }
238
239 return true;
240 }
241
242 /* https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity */
php_dom_pre_insert_is_parent_invalid(xmlNodePtr parent)243 bool php_dom_pre_insert_is_parent_invalid(xmlNodePtr parent)
244 {
245 return parent->type != XML_DOCUMENT_NODE
246 && parent->type != XML_HTML_DOCUMENT_NODE
247 && parent->type != XML_ELEMENT_NODE
248 && parent->type != XML_DOCUMENT_FRAG_NODE;
249 }
250
251 /* https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity */
dom_is_pre_insert_valid_without_step_1(php_libxml_ref_obj * document,xmlNodePtr parentNode,xmlNodePtr node,xmlNodePtr child,xmlDocPtr documentNode)252 static bool dom_is_pre_insert_valid_without_step_1(php_libxml_ref_obj *document, xmlNodePtr parentNode, xmlNodePtr node, xmlNodePtr child, xmlDocPtr documentNode)
253 {
254 ZEND_ASSERT(parentNode != NULL);
255
256 /* 1. If parent is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
257 * => Impossible */
258 ZEND_ASSERT(!php_dom_pre_insert_is_parent_invalid(parentNode));
259
260 if (node->doc != documentNode) {
261 php_dom_throw_error(WRONG_DOCUMENT_ERR, dom_get_strict_error(document));
262 return false;
263 }
264
265 /* 3. If child is non-null and its parent is not parent, then throw a "NotFoundError" DOMException. */
266 if (child != NULL && child->parent != parentNode) {
267 php_dom_throw_error(NOT_FOUND_ERR, dom_get_strict_error(document));
268 return false;
269 }
270
271 bool parent_is_document = parentNode->type == XML_DOCUMENT_NODE || parentNode->type == XML_HTML_DOCUMENT_NODE;
272
273 if (/* 2. If node is a host-including inclusive ancestor of parent, then throw a "HierarchyRequestError" DOMException. */
274 dom_hierarchy(parentNode, node) != SUCCESS
275 /* 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException. */
276 || node->type == XML_ATTRIBUTE_NODE
277 || (php_dom_follow_spec_doc_ref(document) && (
278 node->type == XML_ENTITY_REF_NODE
279 || node->type == XML_ENTITY_NODE
280 || node->type == XML_NOTATION_NODE
281 || node->type == XML_DOCUMENT_NODE
282 || node->type == XML_HTML_DOCUMENT_NODE
283 || node->type >= XML_ELEMENT_DECL))) {
284 php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(document));
285 return false;
286 }
287
288 if (php_dom_follow_spec_doc_ref(document)) {
289 /* 5. If either node is a Text node and parent is a document... */
290 if (parent_is_document && (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE)) {
291 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot insert text as a child of a document", /* strict */ true);
292 return false;
293 }
294
295 /* 5. ..., or node is a doctype and parent is not a document, then throw a "HierarchyRequestError" DOMException. */
296 if (!parent_is_document && node->type == XML_DTD_NODE) {
297 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot insert a document type into anything other than a document", /* strict */ true);
298 return false;
299 }
300
301 /* 6. If parent is a document, and any of the statements below, switched on the interface node implements,
302 * are true, then throw a "HierarchyRequestError" DOMException. */
303 if (parent_is_document) {
304 /* DocumentFragment */
305 if (node->type == XML_DOCUMENT_FRAG_NODE) {
306 if (!php_dom_fragment_insertion_hierarchy_check_pre_insertion(parentNode, node, child)) {
307 return false;
308 }
309 }
310 /* Element */
311 else if (node->type == XML_ELEMENT_NODE) {
312 /* parent has an element child, child is a doctype, or child is non-null and a doctype is following child. */
313 if (php_dom_has_child_of_type(parentNode, XML_ELEMENT_NODE)) {
314 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot have more than one element child in a document", /* strict */ true);
315 return false;
316 }
317 if (child != NULL && (child->type == XML_DTD_NODE || php_dom_has_sibling_following_node(child, XML_DTD_NODE))) {
318 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Document types must be the first child in a document", /* strict */ true);
319 return false;
320 }
321 }
322 /* DocumentType */
323 else if (node->type == XML_DTD_NODE) {
324 /* parent has a doctype child, child is non-null and an element is preceding child, or child is null and parent has an element child. */
325 if (php_dom_has_child_of_type(parentNode, XML_DTD_NODE)) {
326 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot have more than one document type", /* strict */ true);
327 return false;
328 }
329 if ((child != NULL && php_dom_has_sibling_preceding_node(child, XML_ELEMENT_NODE))
330 || (child == NULL && php_dom_has_child_of_type(parentNode, XML_ELEMENT_NODE))) {
331 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Document types must be the first child in a document", /* strict */ true);
332 return false;
333 }
334 }
335 }
336 }
337
338 return true;
339 }
340
dom_free_node_after_zval_single_node_creation(xmlNodePtr node)341 static void dom_free_node_after_zval_single_node_creation(xmlNodePtr node)
342 {
343 /* For the object cases, the user did provide them, so they don't have to be freed as there are still references.
344 * For the newly created text nodes, we do have to free them. */
345 xmlNodePtr next;
346 for (xmlNodePtr child = node->children; child != NULL; child = next) {
347 next = child->next;
348 xmlUnlinkNode(child);
349 if (child->_private == NULL) {
350 xmlFreeNode(child);
351 }
352 }
353 }
354
355 /* https://dom.spec.whatwg.org/#converting-nodes-into-a-node */
dom_zvals_to_single_node(php_libxml_ref_obj * document,xmlNode * contextNode,zval * nodes,uint32_t nodesc)356 xmlNode* dom_zvals_to_single_node(php_libxml_ref_obj *document, xmlNode *contextNode, zval *nodes, uint32_t nodesc)
357 {
358 xmlDoc *documentNode;
359 xmlNode *newNode;
360 dom_object *newNodeObj;
361
362 documentNode = dom_doc_from_context_node(contextNode);
363
364 /* 1. Let node be null. */
365 xmlNodePtr node = NULL;
366
367 /* 2. => handled in the loop. */
368
369 /* 3. If nodes contains one node, then set node to nodes[0]. */
370 if (nodesc == 1) {
371 /* ... and return */
372 if (Z_TYPE_P(nodes) == IS_OBJECT) {
373 return dom_object_get_node(Z_DOMOBJ_P(nodes));
374 } else {
375 ZEND_ASSERT(Z_TYPE_P(nodes) == IS_STRING);
376 return xmlNewDocTextLen(documentNode, BAD_CAST Z_STRVAL_P(nodes), Z_STRLEN_P(nodes));
377 }
378 }
379
380 node = xmlNewDocFragment(documentNode);
381 if (UNEXPECTED(!node)) {
382 return NULL;
383 }
384
385 /* 4. Otherwise, set node to a new DocumentFragment node whose node document is document,
386 * and then append each node in nodes, if any, to it. */
387 for (uint32_t i = 0; i < nodesc; i++) {
388 if (Z_TYPE(nodes[i]) == IS_OBJECT) {
389 newNodeObj = Z_DOMOBJ_P(&nodes[i]);
390 newNode = dom_object_get_node(newNodeObj);
391
392 if (!dom_is_pre_insert_valid_without_step_1(document, node, newNode, NULL, documentNode)) {
393 goto err;
394 }
395
396 if (newNode->parent != NULL) {
397 xmlUnlinkNode(newNode);
398 }
399
400 newNodeObj->document = document;
401 xmlSetTreeDoc(newNode, documentNode);
402
403 if (newNode->type == XML_DOCUMENT_FRAG_NODE) {
404 /* Unpack document fragment nodes, the behaviour differs for different libxml2 versions. */
405 newNode = newNode->children;
406 while (newNode) {
407 xmlNodePtr next = newNode->next;
408 xmlUnlinkNode(newNode);
409 dom_add_child_without_merging(node, newNode);
410 newNode = next;
411 }
412 } else {
413 dom_add_child_without_merging(node, newNode);
414 }
415 } else {
416 /* 2. Replace each string in nodes with a new Text node whose data is the string and node document is document. */
417 ZEND_ASSERT(Z_TYPE(nodes[i]) == IS_STRING);
418
419 /* Text nodes can't violate the hierarchy at this point. */
420 newNode = xmlNewDocTextLen(documentNode, BAD_CAST Z_STRVAL(nodes[i]), Z_STRLEN(nodes[i]));
421 dom_add_child_without_merging(node, newNode);
422 }
423 }
424
425 /* 5. Return node. */
426 return node;
427
428 err:
429 /* For the object cases, the user did provide them, so they don't have to be freed as there are still references.
430 * For the newly created text nodes, we do have to free them. */
431 dom_free_node_after_zval_single_node_creation(node);
432 xmlFree(node);
433 return NULL;
434 }
435
dom_sanity_check_node_list_types(zval * nodes,uint32_t nodesc,zend_class_entry * node_ce)436 static zend_result dom_sanity_check_node_list_types(zval *nodes, uint32_t nodesc, zend_class_entry *node_ce)
437 {
438 for (uint32_t i = 0; i < nodesc; i++) {
439 zend_uchar type = Z_TYPE(nodes[i]);
440 if (type == IS_OBJECT) {
441 const zend_class_entry *ce = Z_OBJCE(nodes[i]);
442
443 if (!instanceof_function(ce, node_ce)) {
444 zend_argument_type_error(i + 1, "must be of type %s|string, %s given", ZSTR_VAL(node_ce->name), zend_zval_type_name(&nodes[i]));
445 return FAILURE;
446 }
447 } else if (type != IS_STRING) {
448 zend_argument_type_error(i + 1, "must be of type %s|string, %s given", ZSTR_VAL(node_ce->name), zend_zval_type_name(&nodes[i]));
449 return FAILURE;
450 }
451 }
452
453 return SUCCESS;
454 }
455
php_dom_pre_insert_helper(xmlNodePtr insertion_point,xmlNodePtr parentNode,xmlNodePtr newchild,xmlNodePtr last)456 static void php_dom_pre_insert_helper(xmlNodePtr insertion_point, xmlNodePtr parentNode, xmlNodePtr newchild, xmlNodePtr last)
457 {
458 if (!insertion_point) {
459 /* Place it as last node */
460 if (parentNode->children) {
461 /* There are children */
462 newchild->prev = parentNode->last;
463 parentNode->last->next = newchild;
464 } else {
465 /* No children, because they moved out when they became a fragment */
466 parentNode->children = newchild;
467 }
468 parentNode->last = last;
469 } else {
470 /* Insert fragment before insertion_point */
471 last->next = insertion_point;
472 if (insertion_point->prev) {
473 insertion_point->prev->next = newchild;
474 newchild->prev = insertion_point->prev;
475 }
476 insertion_point->prev = last;
477 if (parentNode->children == insertion_point) {
478 parentNode->children = newchild;
479 }
480 }
481 }
482
dom_insert_node_list_cleanup(xmlNodePtr node)483 static void dom_insert_node_list_cleanup(xmlNodePtr node)
484 {
485 if (node->_private != NULL) {
486 /* Not a temporary node. */
487 return;
488 }
489 if (node->type == XML_DOCUMENT_FRAG_NODE) {
490 dom_free_node_after_zval_single_node_creation(node);
491 xmlFree(node); /* Don't free the children, now-empty fragment! */
492 } else if (node->type == XML_TEXT_NODE) {
493 ZEND_ASSERT(node->parent == NULL);
494 xmlFreeNode(node);
495 } else {
496 /* Must have been a directly-passed node. */
497 ZEND_ASSERT(node->_private != NULL);
498 }
499 }
500
501 /* https://dom.spec.whatwg.org/#concept-node-pre-insert */
dom_insert_node_list_unchecked(php_libxml_ref_obj * document,xmlNodePtr node,xmlNodePtr parent,xmlNodePtr insertion_point)502 static void dom_insert_node_list_unchecked(php_libxml_ref_obj *document, xmlNodePtr node, xmlNodePtr parent, xmlNodePtr insertion_point)
503 {
504 /* Step 1 should be checked by the caller. */
505
506 if (node->type == XML_DOCUMENT_FRAG_NODE) {
507 /* Steps 2-3 are not applicable here, the condition is impossible. */
508
509 xmlNodePtr newchild = node->children;
510
511 /* 4. Insert node into parent before referenceChild (i.e. insertion_point here because of the impossible condition). */
512 if (newchild) {
513 xmlNodePtr last = node->last;
514 php_dom_pre_insert_helper(insertion_point, parent, newchild, last);
515 dom_fragment_assign_parent_node(parent, node);
516 if (!php_dom_follow_spec_doc_ref(document)) {
517 dom_reconcile_ns_list(parent->doc, newchild, last);
518 }
519 if (parent->doc && newchild->type == XML_DTD_NODE) {
520 parent->doc->intSubset = (xmlDtdPtr) newchild;
521 newchild->parent = (xmlNodePtr) parent->doc;
522 }
523 }
524
525 if (node->_private == NULL) {
526 xmlFree(node);
527 } else {
528 node->children = NULL;
529 node->last = NULL;
530 }
531 } else {
532 /* 2. Let referenceChild be child.
533 * 3. If referenceChild is node, then set referenceChild to node’s next sibling. */
534 if (insertion_point == node) {
535 insertion_point = node->next;
536 }
537
538 /* 4. Insert node into parent before referenceChild. */
539 xmlUnlinkNode(node);
540 php_dom_pre_insert_helper(insertion_point, parent, node, node);
541 node->parent = parent;
542 if (parent->doc && node->type == XML_DTD_NODE) {
543 parent->doc->intSubset = (xmlDtdPtr) node;
544 node->parent = (xmlNodePtr) parent->doc;
545 } else {
546 if (!php_dom_follow_spec_doc_ref(document)) {
547 dom_reconcile_ns(parent->doc, node);
548 }
549 }
550 }
551 }
552
553 /* https://dom.spec.whatwg.org/#concept-node-pre-insert */
php_dom_pre_insert(php_libxml_ref_obj * document,xmlNodePtr node,xmlNodePtr parent,xmlNodePtr insertion_point)554 bool php_dom_pre_insert(php_libxml_ref_obj *document, xmlNodePtr node, xmlNodePtr parent, xmlNodePtr insertion_point)
555 {
556 if (UNEXPECTED(node == NULL)) {
557 return false;
558 }
559
560 /* Step 1 checked here, other steps delegated to other function. */
561 if (dom_is_pre_insert_valid_without_step_1(document, parent, node, insertion_point, parent->doc)) {
562 dom_insert_node_list_unchecked(document, node, parent, insertion_point);
563 return true;
564 } else {
565 dom_insert_node_list_cleanup(node);
566 return false;
567 }
568 }
569
570 /* https://dom.spec.whatwg.org/#concept-node-append */
php_dom_node_append(php_libxml_ref_obj * document,xmlNodePtr node,xmlNodePtr parent)571 void php_dom_node_append(php_libxml_ref_obj *document, xmlNodePtr node, xmlNodePtr parent)
572 {
573 php_dom_pre_insert(document, node, parent, NULL);
574 }
575
576 /* https://dom.spec.whatwg.org/#dom-parentnode-append */
dom_parent_node_append(dom_object * context,zval * nodes,uint32_t nodesc)577 void dom_parent_node_append(dom_object *context, zval *nodes, uint32_t nodesc)
578 {
579 if (UNEXPECTED(dom_sanity_check_node_list_types(nodes, nodesc, dom_get_node_ce(php_dom_follow_spec_doc_ref(context->document))) != SUCCESS)) {
580 return;
581 }
582
583 xmlNode *parentNode = dom_object_get_node(context);
584
585 php_libxml_invalidate_node_list_cache(context->document);
586
587 /* 1. Let node be the result of converting nodes into a node given nodes and this’s node document. */
588 xmlNodePtr node = dom_zvals_to_single_node(context->document, parentNode, nodes, nodesc);
589 if (UNEXPECTED(node == NULL)) {
590 return;
591 }
592
593 /* 2. Append node to this. */
594 php_dom_node_append(context->document, node, parentNode);
595 }
596
597 /* https://dom.spec.whatwg.org/#dom-parentnode-prepend */
dom_parent_node_prepend(dom_object * context,zval * nodes,uint32_t nodesc)598 void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc)
599 {
600 xmlNode *parentNode = dom_object_get_node(context);
601
602 if (parentNode->children == NULL) {
603 dom_parent_node_append(context, nodes, nodesc);
604 return;
605 }
606
607 if (UNEXPECTED(dom_sanity_check_node_list_types(nodes, nodesc, dom_get_node_ce(php_dom_follow_spec_doc_ref(context->document))) != SUCCESS)) {
608 return;
609 }
610
611 php_libxml_invalidate_node_list_cache(context->document);
612
613 /* 1. Let node be the result of converting nodes into a node given nodes and this’s node document. */
614 xmlNodePtr node = dom_zvals_to_single_node(context->document, parentNode, nodes, nodesc);
615 if (UNEXPECTED(node == NULL)) {
616 return;
617 }
618
619 /* 2. Pre-insert node into this before this’s first child. */
620 php_dom_pre_insert(context->document, node, parentNode, parentNode->children);
621 }
622
623 /* https://dom.spec.whatwg.org/#dom-childnode-after */
dom_parent_node_after(dom_object * context,zval * nodes,uint32_t nodesc)624 void dom_parent_node_after(dom_object *context, zval *nodes, uint32_t nodesc)
625 {
626 if (UNEXPECTED(dom_sanity_check_node_list_types(nodes, nodesc, dom_get_node_ce(php_dom_follow_spec_doc_ref(context->document))) != SUCCESS)) {
627 return;
628 }
629
630 xmlNode *thisp = dom_object_get_node(context);
631
632 /* 1. Let parent be this’s parent. */
633 xmlNodePtr parentNode = thisp->parent;
634
635 /* 2. If parent is null, then return. */
636 if (UNEXPECTED(parentNode == NULL)) {
637 return;
638 }
639
640 /* 3. Let viableNextSibling be this’s first following sibling not in nodes; otherwise null. */
641 xmlNodePtr viable_next_sibling = thisp->next;
642 while (viable_next_sibling && dom_is_node_in_list(nodes, nodesc, viable_next_sibling)) {
643 viable_next_sibling = viable_next_sibling->next;
644 }
645
646 php_libxml_invalidate_node_list_cache(context->document);
647
648 /* 4. Let node be the result of converting nodes into a node, given nodes and this’s node document. */
649 xmlNodePtr fragment = dom_zvals_to_single_node(context->document, parentNode, nodes, nodesc);
650
651 /* 5. Pre-insert node into parent before viableNextSibling. */
652 php_dom_pre_insert(context->document, fragment, parentNode, viable_next_sibling);
653 }
654
655 /* https://dom.spec.whatwg.org/#dom-childnode-before */
dom_parent_node_before(dom_object * context,zval * nodes,uint32_t nodesc)656 void dom_parent_node_before(dom_object *context, zval *nodes, uint32_t nodesc)
657 {
658 if (UNEXPECTED(dom_sanity_check_node_list_types(nodes, nodesc, dom_get_node_ce(php_dom_follow_spec_doc_ref(context->document))) != SUCCESS)) {
659 return;
660 }
661
662 xmlNode *thisp = dom_object_get_node(context);
663
664 /* 1. Let parent be this’s parent. */
665 xmlNodePtr parentNode = thisp->parent;
666
667 /* 2. If parent is null, then return. */
668 if (UNEXPECTED(parentNode == NULL)) {
669 return;
670 }
671
672 /* 3. Let viablePreviousSibling be this’s first preceding sibling not in nodes; otherwise null. */
673 xmlNodePtr viable_previous_sibling = thisp->prev;
674 while (viable_previous_sibling && dom_is_node_in_list(nodes, nodesc, viable_previous_sibling)) {
675 viable_previous_sibling = viable_previous_sibling->prev;
676 }
677
678 php_libxml_invalidate_node_list_cache(context->document);
679
680 /* 4. Let node be the result of converting nodes into a node, given nodes and this’s node document. */
681 xmlNodePtr fragment = dom_zvals_to_single_node(context->document, parentNode, nodes, nodesc);
682
683 /* 5. If viable_previous_sibling is null, set it to the parent's first child, otherwise viable_previous_sibling's next sibling. */
684 if (!viable_previous_sibling) {
685 viable_previous_sibling = parentNode->children;
686 } else {
687 viable_previous_sibling = viable_previous_sibling->next;
688 }
689
690 /* 6. Pre-insert node into parent before viablePreviousSibling. */
691 php_dom_pre_insert(context->document, fragment, parentNode, viable_previous_sibling);
692 }
693
dom_child_removal_preconditions(const xmlNode * child,int stricterror)694 static zend_result dom_child_removal_preconditions(const xmlNode *child, int stricterror)
695 {
696 if (dom_node_is_read_only(child) == SUCCESS ||
697 (child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
698 php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
699 return FAILURE;
700 }
701
702 if (!child->parent) {
703 php_dom_throw_error(NOT_FOUND_ERR, stricterror);
704 return FAILURE;
705 }
706
707 if (!dom_node_children_valid(child->parent)) {
708 return FAILURE;
709 }
710
711 xmlNodePtr children = child->parent->children;
712 if (!children) {
713 php_dom_throw_error(NOT_FOUND_ERR, stricterror);
714 return FAILURE;
715 }
716
717 return SUCCESS;
718 }
719
dom_child_node_remove(dom_object * context)720 void dom_child_node_remove(dom_object *context)
721 {
722 xmlNode *child = dom_object_get_node(context);
723 bool stricterror = dom_get_strict_error(context->document);
724
725 if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) {
726 return;
727 }
728
729 php_libxml_invalidate_node_list_cache(context->document);
730
731 xmlUnlinkNode(child);
732 }
733
734 /* https://dom.spec.whatwg.org/#dom-childnode-replacewith */
dom_child_replace_with(dom_object * context,zval * nodes,uint32_t nodesc)735 void dom_child_replace_with(dom_object *context, zval *nodes, uint32_t nodesc)
736 {
737 if (UNEXPECTED(dom_sanity_check_node_list_types(nodes, nodesc, dom_get_node_ce(php_dom_follow_spec_doc_ref(context->document))) != SUCCESS)) {
738 return;
739 }
740
741 xmlNodePtr child = dom_object_get_node(context);
742
743 /* 1. Let parent be this’s parent. */
744 xmlNodePtr parentNode = child->parent;
745
746 /* 2. If parent is null, then return. */
747 if (UNEXPECTED(parentNode == NULL)) {
748 return;
749 }
750
751 /* 3. Let viableNextSibling be this’s first following sibling not in nodes; otherwise null. */
752 xmlNodePtr viable_next_sibling = child->next;
753 while (viable_next_sibling && dom_is_node_in_list(nodes, nodesc, viable_next_sibling)) {
754 viable_next_sibling = viable_next_sibling->next;
755 }
756
757 bool stricterror = dom_get_strict_error(context->document);
758 if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) {
759 return;
760 }
761
762 php_libxml_invalidate_node_list_cache(context->document);
763
764 /* 4. Let node be the result of converting nodes into a node, given nodes and this’s node document. */
765 xmlNodePtr node = dom_zvals_to_single_node(context->document, parentNode, nodes, nodesc);
766 if (UNEXPECTED(node == NULL)) {
767 return;
768 }
769
770 /* Spec step 5-6: perform the replacement */
771 if (dom_is_pre_insert_valid_without_step_1(context->document, parentNode, node, viable_next_sibling, parentNode->doc)) {
772 /* Unlink it unless it became a part of the fragment.
773 * Freeing will be taken care of by the lifetime of the returned dom object. */
774 if (child->parent != node) {
775 xmlUnlinkNode(child);
776 }
777
778 dom_insert_node_list_unchecked(context->document, node, parentNode, viable_next_sibling);
779 } else {
780 dom_insert_node_list_cleanup(node);
781 }
782 }
783
784 /* https://dom.spec.whatwg.org/#dom-parentnode-replacechildren */
dom_parent_node_replace_children(dom_object * context,zval * nodes,uint32_t nodesc)785 void dom_parent_node_replace_children(dom_object *context, zval *nodes, uint32_t nodesc)
786 {
787 if (UNEXPECTED(dom_sanity_check_node_list_types(nodes, nodesc, dom_get_node_ce(php_dom_follow_spec_doc_ref(context->document))) != SUCCESS)) {
788 return;
789 }
790
791 xmlNodePtr thisp = dom_object_get_node(context);
792
793 php_libxml_invalidate_node_list_cache(context->document);
794
795 /* 1. Let node be the result of converting nodes into a node given nodes and this’s node document. */
796 xmlNodePtr node = dom_zvals_to_single_node(context->document, thisp, nodes, nodesc);
797 if (UNEXPECTED(node == NULL)) {
798 return;
799 }
800
801 /* Spec steps 2-3: replace all */
802 if (dom_is_pre_insert_valid_without_step_1(context->document, thisp, node, NULL, thisp->doc)) {
803 dom_remove_all_children(thisp);
804 php_dom_pre_insert(context->document, node, thisp, NULL);
805 } else {
806 dom_insert_node_list_cleanup(node);
807 }
808 }
809
810 #endif
811