diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php
index 31c4bc8a10654..519a3631f39ed 100644
--- a/src/wp-includes/html-api/class-wp-html-tag-processor.php
+++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php
@@ -3811,35 +3811,29 @@ public function set_modifiable_text( string $plaintext_content ): bool {
switch ( $this->get_tag() ) {
case 'SCRIPT':
- /**
- * This is over-protective, but ensures the update doesn't break
- * the HTML structure of the SCRIPT element.
- *
- * More thorough analysis could track the HTML tokenizer states
- * and to ensure that the SCRIPT element closes at the expected
- * SCRIPT close tag as is done in {@see ::skip_script_data()}.
- *
- * A SCRIPT element could be closed prematurely by contents
- * like ``. A SCRIPT element could be prevented from
- * closing by contents like ` └─────┐
+ * │ ▼ │ │
+ * │ ┌─────────────────────────────────────────┐ │
+ * │
+ * │ │ '
+ *
+ * The original source of this graph is included at the bottom of this file.
+ *
+ * @see https://html.spec.whatwg.org/#restrictions-for-contents-of-script-elements
+ * @see wp_html_api_script_element_escaping_diagram_source()
+ *
+ * @since 7.0.0
+ *
+ * @param string $sourcecode Raw contents intended to be serialized into an HTML SCRIPT element.
+ * @return string Escaped form of input contents which will not lead to premature closing of the containing SCRIPT element.
+ */
+ public static function escape_javascript_script_contents( string $sourcecode ): string {
+ $at = 0;
+ $was_at = 0;
+ $end = strlen( $sourcecode );
+ $escaped = '';
+
+ /*
+ * Replace all instances of the ASCII case-insensitive match of " script_data [label="-->"];
+ script_data_escaped -> script_data_double_escaped [label=" script_data [label="-->"];
+ script_data_double_escaped -> script_data_escaped [label="'";
+ labelloc=b;
+}
diff --git a/tests/phpunit/data/html-api/script-element-escaping-diagram.php b/tests/phpunit/data/html-api/script-element-escaping-diagram.php
new file mode 100644
index 0000000000000..20c10f5711347
--- /dev/null
+++ b/tests/phpunit/data/html-api/script-element-escaping-diagram.php
@@ -0,0 +1,13 @@
+` within a comment or ``
+ * within a text/plain SCRIPT tag.
*
* @ticket 61617
+ * @ticket 62797
*
* @dataProvider data_unallowed_modifiable_text_updates
*
* @param string $html_with_nonempty_modifiable_text Will be used to find the test element.
* @param string $invalid_update Update containing possibly-compromising text.
*/
- public function test_rejects_updates_with_unallowed_substrings( string $html_with_nonempty_modifiable_text, string $invalid_update ) {
+ public function test_rejects_dangerous_updates( string $html_with_nonempty_modifiable_text, string $invalid_update ) {
$processor = new WP_HTML_Tag_Processor( $html_with_nonempty_modifiable_text );
while ( '' === $processor->get_modifiable_text() && $processor->next_token() ) {
@@ -466,7 +468,7 @@ public function test_rejects_updates_with_unallowed_substrings( string $html_wit
$this->assertFalse(
$processor->set_modifiable_text( $invalid_update ),
- 'Should have reject possibly-compromising modifiable text update.'
+ 'Should have rejected possibly-compromising modifiable text update.'
);
// Flush updates.
@@ -486,11 +488,152 @@ public function test_rejects_updates_with_unallowed_substrings( string $html_wit
*/
public static function data_unallowed_modifiable_text_updates() {
return array(
- 'Comment with -->' => array( '', 'Comments end in -->' ),
- 'Comment with --!>' => array( '', 'Invalid but legitimate comments end in --!>' ),
- 'SCRIPT with ' => array( '', 'Just a ' ),
- 'SCRIPT with ' => array( '', 'beforeafter' ),
- 'SCRIPT with "', '' => array( '', 'Comments end in -->' ),
+ 'Comment with --!>' => array( '', 'Invalid but legitimate comments end in --!>' ),
+ 'Non-JS SCRIPT with ', '