Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
cee0661
HTML API: Add tests for attribute value input preprocessing.
sirreal Jun 11, 2026
82a26aa
HTML API: Apply input preprocessing in get_attribute().
sirreal Jun 11, 2026
48d8fb4
HTML API: Add tests for class updates over preprocessed values.
sirreal Jun 11, 2026
d1f852c
HTML API: Apply input preprocessing when flushing class updates.
sirreal Jun 11, 2026
020155a
HTML API: Add tests for NULL bytes in attribute names.
sirreal Jun 11, 2026
442e820
HTML API: Replace NULL bytes in comparable attribute names.
sirreal Jun 11, 2026
135157f
HTML API: Add tests for NULL bytes in tag names.
sirreal Jun 11, 2026
5b8ad27
HTML API: Replace NULL bytes in tag names at the read boundary.
sirreal Jun 11, 2026
f6f58fd
HTML API: Add test for NULL bytes in API-supplied class values.
sirreal Jun 11, 2026
ba93ef4
HTML API: Stop replacing NULL bytes in API-supplied class values.
sirreal Jun 11, 2026
9baceb6
HTML API: Avoid re-scanning attribute values without CR or NULL bytes.
sirreal Jun 11, 2026
3b415d1
HTML API: Add tests for character references preceding replaced bytes.
sirreal Jun 11, 2026
8f5e8b2
HTML API: Replace NULL bytes after decoding attribute values.
sirreal Jun 11, 2026
e18f389
HTML API: Detect ambiguous character reference followers by ASCII only.
sirreal Jun 11, 2026
449bf72
HTML API: Add tests for tag-name queries over replaced names.
sirreal Jun 11, 2026
5c52634
HTML API: Match tag-name queries against replaced names.
sirreal Jun 11, 2026
5292c7d
HTML API: Add test for case-insensitive class update flushing.
sirreal Jun 11, 2026
8c26adf
HTML API: Flush class updates for any case spelling of "class".
sirreal Jun 11, 2026
e41d168
HTML API: Pin edge cases of replaced names and document boundaries.
sirreal Jun 11, 2026
78d58d0
HTML API: Add tests for serializing decoded carriage returns.
sirreal Jun 11, 2026
4127e36
HTML API: Serialize decoded carriage returns as character references.
sirreal Jun 11, 2026
9c3302f
HTML API: Pin serialization of NULL bytes in API-supplied values.
sirreal Jun 11, 2026
96c6fb8
HTML API: Consolidate serializer NULL-byte handling.
sirreal Jun 11, 2026
3a74497
HTML API: Pin rawtext serialization and reparse round trips.
sirreal Jun 11, 2026
11967d9
Merge remote-tracking branch 'upstream/trunk' into HEAD
sirreal Jun 15, 2026
88a4d52
Merge remote-tracking branch 'upstream/trunk' into HEAD
sirreal Jun 15, 2026
7f64468
Merge branch 'trunk' into spec-compliant-getters
sirreal Jul 1, 2026
62d682f
Revert irrelevant doc changes
sirreal Jul 1, 2026
b7d4b39
Remove redundant tests
sirreal Jul 1, 2026
30b17da
Remove excessive docs
sirreal Jul 1, 2026
7e451fe
Simplify tag name matching logic
sirreal Jul 1, 2026
b3d15f5
Remove excessive documentation
sirreal Jul 1, 2026
624fe63
simplify comment
sirreal Jul 1, 2026
5293caa
Remove excessive documentation
sirreal Jul 1, 2026
7f77e93
Simplify attribute value getter
sirreal Jul 1, 2026
908f4b3
Ignore new private method
sirreal Jul 1, 2026
fed8e18
Rework new function docs
sirreal Jul 1, 2026
3c61f03
Remove excessive docs
sirreal Jul 1, 2026
480bce8
Reformat comment
sirreal Jul 1, 2026
d701662
Test function types
sirreal Jul 1, 2026
0727c27
Merge branch 'spec-compliant-getters' into html-api-fuzz-fiz/decoded-cr
sirreal Jul 1, 2026
9eda861
Merge branch 'trunk' into html-api-fuzz-fiz/decoded-cr
sirreal Jul 9, 2026
df36b29
Use hex numeric character reference
sirreal Jul 10, 2026
a0ebe96
Merge branch 'trunk' into html-api-fuzz-fiz/decoded-cr
sirreal Jul 10, 2026
c7209f9
Merge branch 'trunk' into html-api-fuzz-fiz/decoded-cr
sirreal Jul 13, 2026
0348b64
Consolidate tests and remove off-topic tests
sirreal Jul 13, 2026
03913f3
Improve escape_text_for_serialization method name and description
sirreal Jul 13, 2026
03fae3c
Tweaks
sirreal Jul 13, 2026
38be23f
Merge branch 'trunk' into html-api-fuzz-fiz/decoded-cr
sirreal Jul 13, 2026
b07735b
Fix unclosed title test
sirreal Jul 13, 2026
d77b882
Merge branch 'trunk' into html-api-fuzz-fiz/decoded-cr
sirreal Jul 13, 2026
7f84a21
Remove redundant function doc line.
sirreal Jul 13, 2026
99b0a9b
Ignore new private method
sirreal Jul 13, 2026
fe56f65
Simplify method documentation
sirreal Jul 13, 2026
5ad2dd2
Merge branch 'trunk' into html-api-fuzz-fiz/decoded-cr
sirreal Jul 13, 2026
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
38 changes: 31 additions & 7 deletions src/wp-includes/html-api/class-wp-html-processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -1387,7 +1387,7 @@ public function serialize_token(): string {
break;

case '#text':
$html .= htmlspecialchars( $this->get_modifiable_text(), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8' );
$html .= self::escape_text_for_serialization( $this->get_modifiable_text() );
break;

// Unlike the `<>` which is interpreted as plaintext, this is ignored entirely.
Expand Down Expand Up @@ -1417,10 +1417,9 @@ public function serialize_token(): string {
return $html;
}

$tag_name = str_replace( "\x00", "\u{FFFD}", $this->get_tag() );
$tag_name = $this->get_tag();
$in_html = 'html' === $this->get_namespace();
$qualified_name = $in_html ? strtolower( $tag_name ) : $this->get_qualified_tag_name();
Comment on lines +1420 to 1422

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are already handled since r62667:

$tag_name = str_replace( "\x00", "\u{FFFD}", substr( $this->html, $this->tag_name_starts_at, $this->tag_name_length ) );

$qualified_name = str_replace( "\x00", "\u{FFFD}", $qualified_name );

if ( $this->is_tag_closer() ) {
$html .= "</{$qualified_name}>";
Expand All @@ -1439,7 +1438,6 @@ public function serialize_token(): string {
$seen_attribute_names = array();
foreach ( $attribute_names as $attribute_name ) {
$qualified_attribute_name = $this->get_qualified_attribute_name( $attribute_name );
$qualified_attribute_name = str_replace( "\x00", "\u{FFFD}", $qualified_attribute_name );
$qualified_attribute_name = wp_scrub_utf8( $qualified_attribute_name );
/**
* Spaces only appear via the foreign attribute adjustment table.
Expand All @@ -1464,11 +1462,10 @@ public function serialize_token(): string {
$value = $this->get_attribute( $attribute_name );

if ( is_string( $value ) ) {
$html .= '="' . htmlspecialchars( $value, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5 ) . '"';
$html .= '="' . self::escape_text_for_serialization( $value ) . '"';
}

$previous_attribute_was_true = true === $value;
$html = str_replace( "\x00", "\u{FFFD}", $html );
}

if ( ! $in_html && $this->has_self_closing_flag() ) {
Expand Down Expand Up @@ -1517,7 +1514,7 @@ public function serialize_token(): string {
break;

default:
$text = htmlspecialchars( $text, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8' );
$text = self::escape_text_for_serialization( $text );
}

$html .= "{$text}</{$qualified_name}>";
Expand All @@ -1526,6 +1523,33 @@ public function serialize_token(): string {
return $html;
}

/**
* Escapes decoded text for HTML serialization.
*
* Use for:
* - Attribute values.
* - Text in ordinary (data-state) elements and in the RCDATA elements
* (TITLE and TEXTAREA).
* - Text in foreign content (elements not in the HTML namespace).
*
* Do not use for text in the RAWTEXT elements (STYLE, XMP, IFRAME,
* NOEMBED, NOFRAMES), HTML SCRIPT elements, or PLAINTEXT elements,
* whose contents serialize without escaping.
*
* @since 7.1.0
Comment thread
sirreal marked this conversation as resolved.
* @ignore
*
* @param string $text Decoded text to escape.
* @return string Escaped text.
*/
private static function escape_text_for_serialization( string $text ): string {
$text = htmlspecialchars( $text, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8' );

$text = str_replace( "\r", '&#xD;', $text );

return str_replace( "\x00", "\u{FFFD}", $text );
}

/**
* Parses next element in the 'initial' insertion mode.
*
Expand Down
39 changes: 39 additions & 0 deletions tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,45 @@ public static function data_provider_normalized_fuzzer_cases_that_should_be_idem
);
}

/**
* Ensures that carriage returns are correctly serialized.
*
* @ticket 65372
*
* @dataProvider data_provider_carriage_returns
*
* @param string $input HTML input containing a decoded carriage return.
* @param string $expected Expected normalized output.
*/
public function test_normalize_serializes_decoded_carriage_returns_as_character_references( string $input, string $expected ): void {
$normalized = WP_HTML_Processor::normalize( $input );

$this->assertSame( $expected, $normalized, 'Should have serialized the carriage return as a character reference.' );
$this->assertSame(
$expected,
WP_HTML_Processor::normalize( $normalized ),
'Normalizing already-normalized HTML should not change the serialized carriage return.'
);
}

/**
* Data provider.
*
* @return array<string, array{string, string}>
*/
public static function data_provider_carriage_returns(): array {
return array(
'Decimal character reference' => array( 'a&#13;b', 'a&#xD;b' ),
'Hex character reference' => array( 'a&#x0D;b', 'a&#xD;b' ),
'RCDATA title' => array( '<title>a&#13;b</title>', '<title>a&#xD;b</title>' ),
'Attribute value' => array( '<p attr="a&#13;b"></p>', '<p attr="a&#xD;b"></p>' ),
'Table text' => array( '<table><td>x&#13;', '<table><tbody><tr><td>x&#xD;</td></tr></tbody></table>' ),
'Template text' => array( '<template>a&#13;b</template>', '<template>a&#xD;b</template>' ),
'Raw CR' => array( "<p title=\"a\rb\"></p>", "<p title=\"a\nb\"></p>" ),
'Raw CRLF pair' => array( "<p title=\"a\r\nb\">", "<p title=\"a\nb\"></p>" ),
);
}

/**
* Data provider.
*
Expand Down
Loading