@@ -124,6 +124,23 @@ int dom_parent_node_child_element_count(dom_object *obj, zval *retval)
124
124
}
125
125
/* }}} */
126
126
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
+
127
144
xmlNode * dom_zvals_to_fragment (php_libxml_ref_obj * document , xmlNode * contextNode , zval * nodes , int nodesc )
128
145
{
129
146
int i ;
@@ -177,17 +194,16 @@ xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNod
177
194
goto hierarchy_request_err ;
178
195
}
179
196
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 ) {
186
202
newNode = xmlCopyNode (newNode , 1 );
187
203
}
188
204
189
205
if (!xmlAddChild (fragment , newNode )) {
190
- if (nodesc > 1 ) {
206
+ if (will_free ) {
191
207
xmlFreeNode (newNode );
192
208
}
193
209
goto hierarchy_request_err ;
@@ -303,25 +319,64 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc)
303
319
xmlFree (fragment );
304
320
}
305
321
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
+
306
350
void dom_parent_node_after (dom_object * context , zval * nodes , int nodesc )
307
351
{
352
+ /* Spec link: https://dom.spec.whatwg.org/#dom-childnode-after */
353
+
308
354
xmlNode * prevsib = dom_object_get_node (context );
309
355
xmlNodePtr newchild , parentNode ;
310
- xmlNode * fragment , * nextsib ;
356
+ xmlNode * fragment ;
311
357
xmlDoc * doc ;
312
- bool afterlastchild ;
313
-
314
- int stricterror = dom_get_strict_error (context -> document );
315
358
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 );
318
365
return ;
319
366
}
320
367
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
+
321
377
doc = prevsib -> doc ;
322
- parentNode = prevsib -> parent ;
323
- nextsib = prevsib -> next ;
324
- afterlastchild = (nextsib == NULL );
378
+
379
+ /* Spec step 4: convert nodes into fragment */
325
380
fragment = dom_zvals_to_fragment (context -> document , parentNode , nodes , nodesc );
326
381
327
382
if (fragment == NULL ) {
@@ -331,40 +386,9 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
331
386
newchild = fragment -> children ;
332
387
333
388
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 );
366
391
367
- newchild -> prev = prevsib ;
368
392
dom_fragment_assign_parent_node (parentNode , fragment );
369
393
dom_reconcile_ns (doc , newchild );
370
394
}
@@ -374,17 +398,34 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
374
398
375
399
void dom_parent_node_before (dom_object * context , zval * nodes , int nodesc )
376
400
{
401
+ /* Spec link: https://dom.spec.whatwg.org/#dom-childnode-before */
402
+
377
403
xmlNode * nextsib = dom_object_get_node (context );
378
- xmlNodePtr newchild , prevsib , parentNode ;
379
- xmlNode * fragment , * afternextsib ;
404
+ xmlNodePtr newchild , parentNode ;
405
+ xmlNode * fragment ;
380
406
xmlDoc * doc ;
381
- bool beforefirstchild ;
382
407
383
- doc = nextsib -> doc ;
384
- prevsib = nextsib -> prev ;
385
- afternextsib = nextsib -> next ;
408
+ /* Spec step 1 */
386
409
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 */
388
429
fragment = dom_zvals_to_fragment (context -> document , parentNode , nodes , nodesc );
389
430
390
431
if (fragment == NULL ) {
@@ -394,37 +435,14 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
394
435
newchild = fragment -> children ;
395
436
396
437
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 ;
418
441
} 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 ;
425
443
}
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 ) ;
428
446
429
447
dom_fragment_assign_parent_node (parentNode , fragment );
430
448
dom_reconcile_ns (doc , newchild );
0 commit comments