Skip to content

Commit e4e65aa

Browse files
committed
1 parent 402b1c2 commit e4e65aa

File tree

7 files changed

+188
-9
lines changed

7 files changed

+188
-9
lines changed

ext/dom/dom_properties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ zend_result dom_element_schema_type_info_read(dom_object *obj, zval *retval);
8787
zend_result dom_element_inner_html_read(dom_object *obj, zval *retval);
8888
zend_result dom_element_inner_html_write(dom_object *obj, zval *newval);
8989
zend_result dom_element_outer_html_read(dom_object *obj, zval *retval);
90+
zend_result dom_element_outer_html_write(dom_object *obj, zval *newval);
9091
zend_result dom_element_class_list_read(dom_object *obj, zval *retval);
9192
zend_result dom_modern_element_substituted_node_value_read(dom_object *obj, zval *retval);
9293
zend_result dom_modern_element_substituted_node_value_write(dom_object *obj, zval *newval);

ext/dom/inner_html_mixin.c

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -341,23 +341,31 @@ static xmlNodePtr dom_xml_fragment_parsing_algorithm(dom_object *obj, const xmlN
341341
return NULL;
342342
}
343343

344-
/* https://w3c.github.io/DOM-Parsing/#the-innerhtml-mixin
345-
* and https://w3c.github.io/DOM-Parsing/#dfn-fragment-parsing-algorithm */
346-
zend_result dom_element_inner_html_write(dom_object *obj, zval *newval)
344+
/* https://w3c.github.io/DOM-Parsing/#dfn-fragment-parsing-algorithm */
345+
static xmlNodePtr dom_parse_fragment(dom_object *obj, xmlNodePtr context_node, const zend_string *input)
347346
{
348-
DOM_PROP_NODE(xmlNodePtr, context_node, obj);
349-
350-
xmlNodePtr fragment;
351347
if (context_node->doc->type == XML_DOCUMENT_NODE) {
352-
fragment = dom_xml_fragment_parsing_algorithm(obj, context_node, Z_STR_P(newval));
348+
return dom_xml_fragment_parsing_algorithm(obj, context_node, input);
353349
} else {
354-
fragment = dom_html_fragment_parsing_algorithm(obj, context_node, Z_STR_P(newval), obj->document->quirks_mode);
350+
return dom_html_fragment_parsing_algorithm(obj, context_node, input, obj->document->quirks_mode);
355351
}
352+
}
353+
354+
/* https://w3c.github.io/DOM-Parsing/#the-innerhtml-mixin */
355+
zend_result dom_element_inner_html_write(dom_object *obj, zval *newval)
356+
{
357+
/* 1. We don't do injection sinks, skip. */
358+
359+
/* 2. Let context be this. */
360+
DOM_PROP_NODE(xmlNodePtr, context_node, obj);
356361

362+
/* 3. Let fragment be the result of invoking the fragment parsing algorithm steps with context and compliantString. */
363+
xmlNodePtr fragment = dom_parse_fragment(obj, context_node, Z_STR_P(newval));
357364
if (fragment == NULL) {
358365
return FAILURE;
359366
}
360367

368+
/* 4. If context is a template element, then set context to the template element's template contents (a DocumentFragment). */
361369
if (php_dom_ns_is_fast(context_node, php_dom_ns_is_html_magic_token) && xmlStrEqual(context_node->name, BAD_CAST "template")) {
362370
context_node = php_dom_ensure_templated_content(php_dom_get_private_data(obj), context_node);
363371
if (context_node == NULL) {
@@ -366,6 +374,7 @@ zend_result dom_element_inner_html_write(dom_object *obj, zval *newval)
366374
}
367375
}
368376

377+
/* 5. Replace all with fragment within context. */
369378
dom_remove_all_children(context_node);
370379
return php_dom_pre_insert(obj->document, fragment, context_node, NULL) ? SUCCESS : FAILURE;
371380
}
@@ -397,4 +406,62 @@ zend_result dom_element_outer_html_read(dom_object *obj, zval *retval)
397406
return SUCCESS;
398407
}
399408

409+
/* https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#the-outerhtml-property */
410+
zend_result dom_element_outer_html_write(dom_object *obj, zval *newval)
411+
{
412+
/* 1. We don't do injection sinks, skip. */
413+
414+
/* 2. Let parent be this's parent. */
415+
DOM_PROP_NODE(xmlNodePtr, this, obj);
416+
xmlNodePtr parent = this->parent;
417+
bool created_parent = false;
418+
419+
/* 3. If parent is null, return. */
420+
if (parent == NULL) {
421+
return SUCCESS;
422+
}
423+
424+
/* 4. If parent is a Document, throw. */
425+
if (parent->type == XML_DOCUMENT_NODE || parent->type == XML_HTML_DOCUMENT_NODE) {
426+
php_dom_throw_error(INVALID_MODIFICATION_ERR, true);
427+
return FAILURE;
428+
}
429+
430+
/* 5. If parent is a DocumentFragment, set parent to the result of creating an element given this's node document, body, and the HTML namespace. */
431+
if (parent->type == XML_DOCUMENT_FRAG_NODE) {
432+
xmlNsPtr html_ns = php_dom_libxml_ns_mapper_ensure_html_ns(php_dom_get_ns_mapper(obj));
433+
434+
parent = xmlNewDocNode(parent->doc, html_ns, BAD_CAST "body", NULL);
435+
created_parent = true;
436+
if (UNEXPECTED(parent == NULL)) {
437+
php_dom_throw_error(INVALID_STATE_ERR, true);
438+
return FAILURE;
439+
}
440+
}
441+
442+
/* 6. Let fragment be the result of invoking the fragment parsing algorithm steps given parent and compliantString. */
443+
xmlNodePtr fragment = dom_parse_fragment(obj, parent, Z_STR_P(newval));
444+
if (fragment == NULL) {
445+
if (created_parent) {
446+
xmlFreeNode(parent);
447+
}
448+
return FAILURE;
449+
}
450+
451+
/* 7. Replace this with fragment within this's parent. */
452+
if (!php_dom_pre_insert(obj->document, fragment, this->parent, this)) {
453+
xmlFreeNode(fragment);
454+
if (created_parent) {
455+
xmlFreeNode(parent);
456+
}
457+
return FAILURE;
458+
}
459+
xmlUnlinkNode(this);
460+
if (created_parent) {
461+
ZEND_ASSERT(parent->children == NULL);
462+
xmlFreeNode(parent);
463+
}
464+
return SUCCESS;
465+
}
466+
400467
#endif

ext/dom/php_dom.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,7 @@ PHP_MINIT_FUNCTION(dom)
11211121
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "previousElementSibling", dom_node_previous_element_sibling_read, NULL);
11221122
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "nextElementSibling", dom_node_next_element_sibling_read, NULL);
11231123
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "innerHTML", dom_element_inner_html_read, dom_element_inner_html_write);
1124-
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "outerHTML", dom_element_outer_html_read, NULL);
1124+
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "outerHTML", dom_element_outer_html_read, dom_element_outer_html_write);
11251125
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "substitutedNodeValue", dom_modern_element_substituted_node_value_read, dom_modern_element_substituted_node_value_write);
11261126
zend_hash_merge(&dom_modern_element_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
11271127
DOM_OVERWRITE_PROP_HANDLER(&dom_modern_element_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
Test writing Element::$outerHTML on HTML documents
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = Dom\HTMLDocument::createFromString('<p>foo</p>', LIBXML_NOERROR);
9+
$p = $dom->body->firstChild;
10+
$p->outerHTML = '<div></div>&nbsp;<p>'; // intentionally unclosed
11+
echo $dom->saveXML(), "\n";
12+
echo $dom->saveHtml(), "\n";
13+
$div = $dom->body->firstChild;
14+
$div->outerHTML = "invalid\xffutf-8𐍈𐍈𐍈";
15+
echo $dom->saveXML(), "\n";
16+
echo $dom->saveHtml(), "\n";
17+
18+
$dom->body->outerHTML = '<template><p>foo</p></template>';
19+
var_dump($dom->body->querySelector('p')); // Should be NULL because the template contents do not participate in the DOM tree
20+
echo $dom->saveXML(), "\n";
21+
echo $dom->saveHtml(), "\n";
22+
23+
?>
24+
--EXPECT--
25+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
26+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div></div> <p></p></body></html>
27+
<html><head></head><body><div></div>&nbsp;<p></p></body></html>
28+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
29+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>invalid�utf-8𐍈𐍈𐍈 <p></p></body></html>
30+
<html><head></head><body>invalid�utf-8𐍈𐍈𐍈&nbsp;<p></p></body></html>
31+
NULL
32+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
33+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><head><template><p>foo</p></template></head><body></body></html>
34+
<html><head></head><head><template><p>foo</p></template></head><body></body></html>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Test reading Element::$outerHTML on HTML documents - invalid tree variation
3+
--EXTENSIONS--
4+
dom
5+
--CREDITS--
6+
Dennis Snell
7+
--FILE--
8+
<?php
9+
10+
$dom = Dom\HTMLDocument::createFromString('<a href="#one"><p>Link</p></a>', LIBXML_NOERROR);
11+
$p = $dom->body->querySelector('p');
12+
$p->outerHTML = '<a href="#two">Another Link</a>';
13+
echo $dom->saveHTML();
14+
15+
?>
16+
--EXPECT--
17+
<html><head></head><body><a href="#one"><a href="#two">Another Link</a></a></body></html>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
Test writing Element::$outerHTML on XML documents
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = Dom\XMLDocument::createFromString("<root xmlns='urn:a'><x/></root>");
9+
$dom->documentElement->firstChild->outerHTML = '<x xmlns="urn:a"><p>foo</p><p xmlns="urn:x">bar</p></x>';
10+
echo $dom->saveXML(), "\n";
11+
12+
$dom->documentElement->firstChild->outerHTML = $dom->documentElement->firstChild->outerHTML;
13+
$element = $dom->documentElement->firstChild->firstChild;
14+
echo $dom->saveXML(), "\n";
15+
16+
$dom->documentElement->firstChild->outerHTML = 'tést';
17+
echo $dom->saveXML(), "\n";
18+
19+
var_dump($element->tagName);
20+
21+
$fragment = $dom->createDocumentFragment();
22+
$fragment->appendChild($dom->createElement('p'));
23+
$fragment->firstChild->outerHTML = '<strong>bar</strong>';
24+
echo $dom->saveXML($fragment), "\n";
25+
26+
?>
27+
--EXPECT--
28+
<?xml version="1.0" encoding="UTF-8"?>
29+
<root xmlns="urn:a"><x xmlns="urn:a"><p>foo</p><p xmlns="urn:x">bar</p></x></root>
30+
<?xml version="1.0" encoding="UTF-8"?>
31+
<root xmlns="urn:a"><x xmlns="urn:a"><p>foo</p><p xmlns="urn:x">bar</p></x></root>
32+
<?xml version="1.0" encoding="UTF-8"?>
33+
<root xmlns="urn:a">tést</root>
34+
string(1) "p"
35+
<strong xmlns="http://www.w3.org/1999/xhtml">bar</strong>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Test writing Element::$outerHTML on XML documents - error cases
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = Dom\XMLDocument::createFromString('<root/>');
9+
try {
10+
$dom->documentElement->outerHTML = '<x/>';
11+
} catch (DOMException $e) {
12+
echo $e->getMessage(), "\n";
13+
}
14+
15+
$dom = Dom\XMLDocument::createFromString('<root><child/></root>');
16+
try {
17+
$dom->documentElement->firstChild->outerHTML = '<!DOCTYPE html>';
18+
} catch (DOMException $e) {
19+
echo $e->getMessage(), "\n";
20+
}
21+
22+
?>
23+
--EXPECT--
24+
Invalid Modification Error
25+
XML fragment is not well-formed

0 commit comments

Comments
 (0)