HTML API: Escape syntax characters in RCDATA - #13327
Conversation
c563b48 to
c3de662
Compare
c3de662 to
686dc5e
Compare
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
| $plaintext_content = strtr( | ||
| $plaintext_content, | ||
| array( | ||
| '<' => '<', | ||
| '&' => '&', | ||
| '>' => '>', | ||
| ) | ||
| ); |
There was a problem hiding this comment.
Looks right: https://3v4l.org/0Kf28#veol
<title>
<&>: &lt;&amp;&gt;
</title>Parsed as:
<&>: <&>
|
|
||
| case 'TEXTAREA': | ||
| case 'TITLE': | ||
| $plaintext_content = strtr( |
There was a problem hiding this comment.
A comment is probably warranted here to say that these replacements are not required for a spec-complaint HTML parser. But they are done for the sake of other parsers, including DOMDocument.
I'm surprised to see that DOMDocument parses elements as children of TITLE! Happily this is fixed in Dom\HTMLDocument: https://3v4l.org/oDUI3#veol
Test code
<?php
$html = '
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>We <em>love</em> HTML!</title>
</head>
<body>
</body>
</html>
';
function run_tests( $document ) {
$title = $document->getElementsByTagName( 'title' )->item( 0 );
if ( $title->childNodes->length !== 1 ) {
echo "FAIL: Title tag does not have the the expected single text child. Has {$title->childNodes->length} children.\n";
} else {
echo "PASS: Title tag only has a single child.\n";
}
if ( $title->getElementsByTagName( 'em' )->length !== 0 ) {
echo "FAIL: Title tag unexpectedly has a parsed EM child.\n";
} else {
echo "PASS: Title tag has no child EM element.\n";
}
if ( str_contains( $title->textContent, '<em>' ) ) {
echo "PASS: HTML tag <em> was not parsed. Text content: {$title->textContent}\n";
} else {
echo "FAIL: HTML tag <em> was parsed. Text content: {$title->textContent}\n";
}
}
echo "# DOMDocument:\n";
$old_document = new DOMDocument();
$old_document->loadHTML( $html );
run_tests( $old_document );
echo "\n";
echo "# Dom\HTMLDocument:\n";
if ( class_exists( Dom\HTMLDocument::class ) ) {
$new_document = Dom\HTMLDocument::createFromString( $html );
run_tests( $new_document );
} else {
echo "(Not available)\n";
}
sirreal
left a comment
There was a problem hiding this comment.
This is probably an improvement overall for anything inspecting HTML downstream.
Assume HTML like: <title><foo><bar></title>
There are two obvious ways to mis-parse this:
<foo>treated as a start tag (it's the text<foo>)<bar>not decoded (it's the text<bar>).
This change would eliminate the first category <foo>, while leaving the second category <bar> exactly the same. The tradeoff here is that naive parsers are more likely to confuse the text <foo> for an element, and if they don't correctly handle decoding… well that would already be broken.
I'm in favor of this, it's perfectly valid and correct HTML and simplifies the surface area of HTML trivia that needs to be correctly implemented downstream.
In some related CSS work, I made a similar decision. It's best to remove possibly confusing syntax characters entirely where escaping is possible.
| $plaintext_content = preg_replace_callback( | ||
| "~</(?P<TAG_NAME>{$this->get_tag()})~i", | ||
| static function ( $tag_match ) { | ||
| return "</{$tag_match['TAG_NAME']}"; | ||
| }, | ||
| $plaintext_content | ||
| ); |
There was a problem hiding this comment.
This becomes redundant if we're already escaping <>& syntax characters.
Status
Description
Characters in
TITLEandTEXTAREAaren’t required to be escaped, but doing so might prevent downstream parsers from mis-parsing content inside of these elements as markup, when in fact they are plaintext.This patch escapes the content by default to prevent such a scenario.