Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions src/wp-includes/html-api/class-wp-html-tag-processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -3815,7 +3815,7 @@ public function get_modifiable_text(): string {
? $this->lexical_updates['modifiable text']->text
: substr( $this->html, $this->text_starts_at, $this->text_length );

/*
/**
* An enqueued processing instruction update holds normalized raw
* syntax spanning from the end of the target through the end of
* the token: a separating space, the data, and the `?>` closer.
Expand Down Expand Up @@ -4161,12 +4161,19 @@ static function ( $tag_match ) {

case 'TEXTAREA':
case 'TITLE':
$plaintext_content = preg_replace_callback(
"~</(?P<TAG_NAME>{$this->get_tag()})~i",
static function ( $tag_match ) {
return "&lt;/{$tag_match['TAG_NAME']}";
},
$plaintext_content
/**
* While not expressly required, escaping syntax characters in these
* elements will help avoid problems with downstream parser which
* attempt to parse tags and other markup within. {@see \DOMDocument},
* for example, will claim to find elements as children of a `TITLE`.
*/
$plaintext_content = strtr(
Comment thread
dmsnell marked this conversation as resolved.
$plaintext_content,
array(
'<' => '&lt;',
'&' => '&amp;',
'>' => '&gt;',
)
);
Comment thread
dmsnell marked this conversation as resolved.

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,9 +414,10 @@ public function test_updates_basic_modifiable_text_on_supported_nodes( string $h
'Should have modified the text at the target node.'
);

$this->assertSame(
$this->assertEqualHTML(
$transformed,
$processor->get_updated_html(),
'<body>',
"Should have transformed the HTML as expected when modifying the target node's modifiable text."
);
}
Expand Down Expand Up @@ -577,6 +578,79 @@ public function test_replaces_previous_processing_instruction_data_update(): voi
);
}

/**
* Ensures that RCDATA contents are eagerly escaped, despite being
* optional, to protect downstream parsers from misparsing.
*
* @ticket {TICKET_NUMBER}
*
* @dataProvider data_rcdata_element_names
*
* @param 'TITLE'|'TEXTAREA' $element_name Which RCDATA element to verify.
*/
public function test_escapes_rcdata_content( string $element_name ): void {
$text = 'the <img> is text';
$html = "<{$element_name}>{$text}</{$element_name}>";
$processor = new WP_HTML_Tag_Processor( $html );

$this->assertTrue(
$processor->next_token(),
"Failed to advance into the {$element_name} element: check test setup."
);

/*
* While this may look like a no-op, it should change the inner text
* from `<img>` to `&lt;img&gt;`, which will be asserted below.
*/
$this->assertTrue(
$processor->set_modifiable_text( $text ),
'Failed to set the modifiable text: check test setup.'
);

$output = $processor->get_updated_html();

// The updated output must still remain equivalent to the input.
$this->assertEqualHTML(
$html,
$output,
'<body>',
'Failed to update modifiable text without changing the parsed HTML.'
);

// Ensure that the content is not left unescaped.
$this->assertStringNotContainsString(
'<img>',
$output,
'Should have escaped the <img> into &lt;img&lt;.'
);

// Finally, ensure that weaker parsers still interpret this correctly.
if ( class_exists( '\DOMDocument' ) ) {
$dom = new \DOMDocument();
$dom->loadHTML( $output, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOERROR | LIBXML_NOWARNING );

$first_child = $dom->getElementsByTagName( strtolower( $element_name ) )->item( 0 )->childNodes[0];

$this->assertSame(
$first_child->nodeValue,
$text,
'Failed to protect weaker parsers from detecting elements as RCDATA content.'
);
}
}

/**
* Data provider.
*
* @return array[]
*/
public static function data_rcdata_element_names(): array {
return array(
array( 'TEXTAREA' ),
array( 'TITLE' ),
);
}

/**
* Ensures that updates with potentially-compromising values aren't accepted.
*
Expand Down
Loading