Skip to content

Commit af72727

Browse files
committed
Merge branch 'PHP-8.1' into PHP-8.2
* PHP-8.1: Fix GH-11288 and GH-11289 and GH-11290 and GH-9142: DOMExceptions and segfaults with replaceWith
2 parents 5b8e941 + cba335d commit af72727

10 files changed

+490
-173
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
?? ??? ????, PHP 8.2.8
44

5+
- DOM:
6+
. Fixed bugs GH-11288 and GH-11289 and GH-11290 and GH-9142 (DOMExceptions
7+
and segfaults with replaceWith). (nielsdos)
8+
59
- Opcache:
610
. Fix allocation loop in zend_shared_alloc_startup(). (nielsdos)
711
. Access violation on smm_shared_globals with ALLOC_FALLBACK. (KoudelkaB)

ext/dom/parentnode.c

Lines changed: 103 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,23 @@ int dom_parent_node_child_element_count(dom_object *obj, zval *retval)
124124
}
125125
/* }}} */
126126

127+
static bool dom_is_node_in_list(const zval *nodes, int nodesc, const xmlNodePtr node_to_find)
128+
{
129+
for (int i = 0; i < nodesc; i++) {
130+
if (Z_TYPE(nodes[i]) == IS_OBJECT) {
131+
const zend_class_entry *ce = Z_OBJCE(nodes[i]);
132+
133+
if (instanceof_function(ce, dom_node_class_entry)) {
134+
if (dom_object_get_node(Z_DOMOBJ_P(nodes + i)) == node_to_find) {
135+
return true;
136+
}
137+
}
138+
}
139+
}
140+
141+
return false;
142+
}
143+
127144
xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNode, zval *nodes, int nodesc)
128145
{
129146
int i;
@@ -177,17 +194,16 @@ xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNod
177194
goto hierarchy_request_err;
178195
}
179196

180-
/*
181-
* xmlNewDocText function will always returns same address to the second parameter if the parameters are greater than or equal to three.
182-
* If it's text, that's fine, but if it's an object, it can cause invalid pointer because many new nodes point to the same memory address.
183-
* So we must copy the new node to avoid this situation.
184-
*/
185-
if (nodesc > 1) {
197+
/* Citing from the docs (https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/libxml2-tree.html#xmlAddChild):
198+
* "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)".
199+
* So we must take a copy if this situation arises to prevent a use-after-free. */
200+
bool will_free = newNode->type == XML_TEXT_NODE && fragment->last && fragment->last->type == XML_TEXT_NODE;
201+
if (will_free) {
186202
newNode = xmlCopyNode(newNode, 1);
187203
}
188204

189205
if (!xmlAddChild(fragment, newNode)) {
190-
if (nodesc > 1) {
206+
if (will_free) {
191207
xmlFreeNode(newNode);
192208
}
193209
goto hierarchy_request_err;
@@ -303,25 +319,64 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc)
303319
xmlFree(fragment);
304320
}
305321

322+
static void dom_pre_insert(xmlNodePtr insertion_point, xmlNodePtr parentNode, xmlNodePtr newchild, xmlNodePtr fragment)
323+
{
324+
if (!insertion_point) {
325+
/* Place it as last node */
326+
if (parentNode->children) {
327+
/* There are children */
328+
fragment->last->prev = parentNode->last;
329+
newchild->prev = parentNode->last->prev;
330+
parentNode->last->next = newchild;
331+
} else {
332+
/* No children, because they moved out when they became a fragment */
333+
parentNode->children = newchild;
334+
parentNode->last = newchild;
335+
}
336+
} else {
337+
/* Insert fragment before insertion_point */
338+
fragment->last->next = insertion_point;
339+
if (insertion_point->prev) {
340+
insertion_point->prev->next = newchild;
341+
newchild->prev = insertion_point->prev;
342+
}
343+
insertion_point->prev = newchild;
344+
if (parentNode->children == insertion_point) {
345+
parentNode->children = newchild;
346+
}
347+
}
348+
}
349+
306350
void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
307351
{
352+
/* Spec link: https://dom.spec.whatwg.org/#dom-childnode-after */
353+
308354
xmlNode *prevsib = dom_object_get_node(context);
309355
xmlNodePtr newchild, parentNode;
310-
xmlNode *fragment, *nextsib;
356+
xmlNode *fragment;
311357
xmlDoc *doc;
312-
bool afterlastchild;
313-
314-
int stricterror = dom_get_strict_error(context->document);
315358

316-
if (!prevsib->parent) {
317-
php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
359+
/* Spec step 1 */
360+
parentNode = prevsib->parent;
361+
/* Spec step 2 */
362+
if (!parentNode) {
363+
int stricterror = dom_get_strict_error(context->document);
364+
php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
318365
return;
319366
}
320367

368+
/* Spec step 3: find first following child not in nodes; otherwise null */
369+
xmlNodePtr viable_next_sibling = prevsib->next;
370+
while (viable_next_sibling) {
371+
if (!dom_is_node_in_list(nodes, nodesc, viable_next_sibling)) {
372+
break;
373+
}
374+
viable_next_sibling = viable_next_sibling->next;
375+
}
376+
321377
doc = prevsib->doc;
322-
parentNode = prevsib->parent;
323-
nextsib = prevsib->next;
324-
afterlastchild = (nextsib == NULL);
378+
379+
/* Spec step 4: convert nodes into fragment */
325380
fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
326381

327382
if (fragment == NULL) {
@@ -331,40 +386,9 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
331386
newchild = fragment->children;
332387

333388
if (newchild) {
334-
/* first node and last node are both both parameters to DOMElement::after() method so nextsib and prevsib are null. */
335-
if (!parentNode->children) {
336-
prevsib = nextsib = NULL;
337-
} else if (afterlastchild) {
338-
/*
339-
* The new node will be inserted after last node, prevsib is last node.
340-
* The first node is the parameter to DOMElement::after() if parentNode->children == prevsib is true
341-
* and prevsib does not change, otherwise prevsib is parentNode->last (first node).
342-
*/
343-
prevsib = parentNode->children == prevsib ? prevsib : parentNode->last;
344-
} else {
345-
/*
346-
* The new node will be inserted after first node, prevsib is first node.
347-
* The first node is not the parameter to DOMElement::after() if parentNode->children == prevsib is true
348-
* and prevsib does not change otherwise prevsib is null to mean that parentNode->children is the new node.
349-
*/
350-
prevsib = parentNode->children == prevsib ? prevsib : NULL;
351-
}
352-
353-
if (prevsib) {
354-
fragment->last->next = prevsib->next;
355-
if (prevsib->next) {
356-
prevsib->next->prev = fragment->last;
357-
}
358-
prevsib->next = newchild;
359-
} else {
360-
parentNode->children = newchild;
361-
if (nextsib) {
362-
fragment->last->next = nextsib;
363-
nextsib->prev = fragment->last;
364-
}
365-
}
389+
/* Step 5: place fragment into the parent before viable_next_sibling */
390+
dom_pre_insert(viable_next_sibling, parentNode, newchild, fragment);
366391

367-
newchild->prev = prevsib;
368392
dom_fragment_assign_parent_node(parentNode, fragment);
369393
dom_reconcile_ns(doc, newchild);
370394
}
@@ -374,17 +398,34 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
374398

375399
void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
376400
{
401+
/* Spec link: https://dom.spec.whatwg.org/#dom-childnode-before */
402+
377403
xmlNode *nextsib = dom_object_get_node(context);
378-
xmlNodePtr newchild, prevsib, parentNode;
379-
xmlNode *fragment, *afternextsib;
404+
xmlNodePtr newchild, parentNode;
405+
xmlNode *fragment;
380406
xmlDoc *doc;
381-
bool beforefirstchild;
382407

383-
doc = nextsib->doc;
384-
prevsib = nextsib->prev;
385-
afternextsib = nextsib->next;
408+
/* Spec step 1 */
386409
parentNode = nextsib->parent;
387-
beforefirstchild = !prevsib;
410+
/* Spec step 2 */
411+
if (!parentNode) {
412+
int stricterror = dom_get_strict_error(context->document);
413+
php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
414+
return;
415+
}
416+
417+
/* Spec step 3: find first following child not in nodes; otherwise null */
418+
xmlNodePtr viable_previous_sibling = nextsib->prev;
419+
while (viable_previous_sibling) {
420+
if (!dom_is_node_in_list(nodes, nodesc, viable_previous_sibling)) {
421+
break;
422+
}
423+
viable_previous_sibling = viable_previous_sibling->prev;
424+
}
425+
426+
doc = nextsib->doc;
427+
428+
/* Spec step 4: convert nodes into fragment */
388429
fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
389430

390431
if (fragment == NULL) {
@@ -394,37 +435,14 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
394435
newchild = fragment->children;
395436

396437
if (newchild) {
397-
/* first node and last node are both both parameters to DOMElement::before() method so nextsib is null. */
398-
if (!parentNode->children) {
399-
nextsib = NULL;
400-
} else if (beforefirstchild) {
401-
/*
402-
* The new node will be inserted before first node, nextsib is first node and afternextsib is last node.
403-
* The first node is not the parameter to DOMElement::before() if parentNode->children == nextsib is true
404-
* and nextsib does not change, otherwise nextsib is the last node.
405-
*/
406-
nextsib = parentNode->children == nextsib ? nextsib : afternextsib;
407-
} else {
408-
/*
409-
* The new node will be inserted before last node, prevsib is first node and nestsib is last node.
410-
* The first node is not the parameter to DOMElement::before() if parentNode->children == prevsib is true
411-
* but last node may be, so use prevsib->next to determine the value of nextsib, otherwise nextsib does not change.
412-
*/
413-
nextsib = parentNode->children == prevsib ? prevsib->next : nextsib;
414-
}
415-
416-
if (parentNode->children == nextsib) {
417-
parentNode->children = newchild;
438+
/* Step 5: if viable_previous_sibling is null, set it to the parent's first child, otherwise viable_previous_sibling's next sibling */
439+
if (!viable_previous_sibling) {
440+
viable_previous_sibling = parentNode->children;
418441
} else {
419-
prevsib->next = newchild;
420-
}
421-
422-
fragment->last->next = nextsib;
423-
if (nextsib) {
424-
nextsib->prev = fragment->last;
442+
viable_previous_sibling = viable_previous_sibling->next;
425443
}
426-
427-
newchild->prev = prevsib;
444+
/* Step 6: place fragment into the parent after viable_previous_sibling */
445+
dom_pre_insert(viable_previous_sibling, parentNode, newchild, fragment);
428446

429447
dom_fragment_assign_parent_node(parentNode, fragment);
430448
dom_reconcile_ns(doc, newchild);

0 commit comments

Comments
 (0)