@@ -1387,7 +1387,7 @@ public function serialize_token(): string {
13871387 break ;
13881388
13891389 case '#text ' :
1390- $ html .= htmlspecialchars ( $ this ->get_modifiable_text (), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5 , ' UTF-8 ' );
1390+ $ html .= self :: escape_text_for_serialization ( $ this ->get_modifiable_text () );
13911391 break ;
13921392
13931393 // Unlike the `<>` which is interpreted as plaintext, this is ignored entirely.
@@ -1417,10 +1417,9 @@ public function serialize_token(): string {
14171417 return $ html ;
14181418 }
14191419
1420- $ tag_name = str_replace ( "\x00" , "\u{FFFD}" , $ this ->get_tag () );
1420+ $ tag_name = $ this ->get_tag ();
14211421 $ in_html = 'html ' === $ this ->get_namespace ();
14221422 $ qualified_name = $ in_html ? strtolower ( $ tag_name ) : $ this ->get_qualified_tag_name ();
1423- $ qualified_name = str_replace ( "\x00" , "\u{FFFD}" , $ qualified_name );
14241423
14251424 if ( $ this ->is_tag_closer () ) {
14261425 $ html .= "</ {$ qualified_name }> " ;
@@ -1439,7 +1438,6 @@ public function serialize_token(): string {
14391438 $ seen_attribute_names = array ();
14401439 foreach ( $ attribute_names as $ attribute_name ) {
14411440 $ qualified_attribute_name = $ this ->get_qualified_attribute_name ( $ attribute_name );
1442- $ qualified_attribute_name = str_replace ( "\x00" , "\u{FFFD}" , $ qualified_attribute_name );
14431441 $ qualified_attribute_name = wp_scrub_utf8 ( $ qualified_attribute_name );
14441442 /**
14451443 * Spaces only appear via the foreign attribute adjustment table.
@@ -1464,11 +1462,10 @@ public function serialize_token(): string {
14641462 $ value = $ this ->get_attribute ( $ attribute_name );
14651463
14661464 if ( is_string ( $ value ) ) {
1467- $ html .= '=" ' . htmlspecialchars ( $ value, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5 ) . '" ' ;
1465+ $ html .= '=" ' . self :: escape_text_for_serialization ( $ value ) . '" ' ;
14681466 }
14691467
14701468 $ previous_attribute_was_true = true === $ value ;
1471- $ html = str_replace ( "\x00" , "\u{FFFD}" , $ html );
14721469 }
14731470
14741471 if ( ! $ in_html && $ this ->has_self_closing_flag () ) {
@@ -1517,7 +1514,7 @@ public function serialize_token(): string {
15171514 break ;
15181515
15191516 default :
1520- $ text = htmlspecialchars ( $ text, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5 , ' UTF-8 ' );
1517+ $ text = self :: escape_text_for_serialization ( $ text );
15211518 }
15221519
15231520 $ html .= "{$ text }</ {$ qualified_name }> " ;
@@ -1526,6 +1523,33 @@ public function serialize_token(): string {
15261523 return $ html ;
15271524 }
15281525
1526+ /**
1527+ * Escapes decoded text for HTML serialization.
1528+ *
1529+ * Use for:
1530+ * - Attribute values.
1531+ * - Text in ordinary (data-state) elements and in the RCDATA elements
1532+ * (TITLE and TEXTAREA).
1533+ * - Text in foreign content (elements not in the HTML namespace).
1534+ *
1535+ * Do not use for text in the RAWTEXT elements (STYLE, XMP, IFRAME,
1536+ * NOEMBED, NOFRAMES), HTML SCRIPT elements, or PLAINTEXT elements,
1537+ * whose contents serialize without escaping.
1538+ *
1539+ * @since 7.1.0
1540+ * @ignore
1541+ *
1542+ * @param string $text Decoded text to escape.
1543+ * @return string Escaped text.
1544+ */
1545+ private static function escape_text_for_serialization ( string $ text ): string {
1546+ $ text = htmlspecialchars ( $ text , ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5 , 'UTF-8 ' );
1547+
1548+ $ text = str_replace ( "\r" , '
 ' , $ text );
1549+
1550+ return str_replace ( "\x00" , "\u{FFFD}" , $ text );
1551+ }
1552+
15291553 /**
15301554 * Parses next element in the 'initial' insertion mode.
15311555 *
0 commit comments