Skip to content

Commit a66d1d5

Browse files
committed
Escape script modifiable text
1 parent bf2b7a4 commit a66d1d5

File tree

1 file changed

+137
-1
lines changed

1 file changed

+137
-1
lines changed

src/wp-includes/html-api/class-wp-html-tag-processor.php

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3736,7 +3736,26 @@ public function set_modifiable_text( string $plaintext_content ): bool {
37363736
* that previously worked. Resolve this by not sending `</script`
37373737
*/
37383738
if ( false !== stripos( $plaintext_content, '</script' ) ) {
3739-
return false;
3739+
/*
3740+
* JavaScript can be safely escaped.
3741+
* Non-JavaScript script tags have unknown semantics.
3742+
*
3743+
* @todo consider applying to JSON and importmap script tags as well.
3744+
*/
3745+
if ( $this->is_javascript_script_tag() ) {
3746+
$plaintext_content = preg_replace_callback(
3747+
'~<(/?)(s)(cript)~i',
3748+
static function ( $matches ) {
3749+
$escaped_s_char = 's' === $matches[2]
3750+
? '\u0073'
3751+
: '\u0053';
3752+
return "<{$matches[1]}{$escaped_s_char}{$matches[3]}";
3753+
},
3754+
$plaintext_content
3755+
);
3756+
} else {
3757+
return false;
3758+
}
37403759
}
37413760

37423761
$this->lexical_updates['modifiable text'] = new WP_HTML_Text_Replacement(
@@ -3794,6 +3813,123 @@ static function ( $tag_match ) {
37943813
return false;
37953814
}
37963815

3816+
/**
3817+
* Indicates if the currently matched tag is a JavaScript script tag.
3818+
*
3819+
* @see https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
3820+
*
3821+
* @since {WP_VERSION}
3822+
*
3823+
* @return boolean True if the script tag will be evaluated as JavaScript.
3824+
*/
3825+
public function is_javascript_script_tag(): bool {
3826+
if ( 'SCRIPT' !== $this->get_tag() || $this->get_namespace() !== 'html' ) {
3827+
return false;
3828+
}
3829+
3830+
/*
3831+
* > If any of the following are true:
3832+
* > - el has a type attribute whose value is the empty string;
3833+
* > - el has no type attribute but it has a language attribute and that attribute's
3834+
* > value is the empty string; or
3835+
* > - el has neither a type attribute nor a language attribute,
3836+
* > then let the script block's type string for this script element be "text/javascript".
3837+
*/
3838+
$type_attr = $this->get_attribute( 'type' );
3839+
$language_attr = $this->get_attribute( 'language' );
3840+
3841+
if ( true === $type_attr || '' === $type_attr ) {
3842+
return true;
3843+
}
3844+
if (
3845+
null === $type_attr && (
3846+
true === $language_attr ||
3847+
'' === $language_attr ||
3848+
null === $language_attr
3849+
)
3850+
) {
3851+
return true;
3852+
}
3853+
3854+
/*
3855+
* > Otherwise, if el has a type attribute, then let the script block's type string be
3856+
* > the value of that attribute with leading and trailing ASCII whitespace stripped.
3857+
* > Otherwise, el has a non-empty language attribute; let the script block's type string
3858+
* > be the concatenation of "text/" and the value of el's language attribute.
3859+
*/
3860+
$type_string = $type_attr ? trim( $type_attr, " \t\f\r\n" ) : "text/{$language_attr}";
3861+
3862+
/*
3863+
* > If the script block's type string is a JavaScript MIME type essence match, then
3864+
* > set el's type to "classic".
3865+
*
3866+
* > A string is a JavaScript MIME type essence match if it is an ASCII case-insensitive
3867+
* > match for one of the JavaScript MIME type essence strings.
3868+
3869+
* > A JavaScript MIME type is any MIME type whose essence is one of the following:
3870+
* >
3871+
* > - application/ecmascript
3872+
* > - application/javascript
3873+
* > - application/x-ecmascript
3874+
* > - application/x-javascript
3875+
* > - text/ecmascript
3876+
* > - text/javascript
3877+
* > - text/javascript1.0
3878+
* > - text/javascript1.1
3879+
* > - text/javascript1.2
3880+
* > - text/javascript1.3
3881+
* > - text/javascript1.4
3882+
* > - text/javascript1.5
3883+
* > - text/jscript
3884+
* > - text/livescript
3885+
* > - text/x-ecmascript
3886+
* > - text/x-javascript
3887+
*
3888+
* @see https://mimesniff.spec.whatwg.org/#javascript-mime-type
3889+
* @see https://mimesniff.spec.whatwg.org/#javascript-mime-type-essence-match
3890+
*/
3891+
switch ( strtolower( $type_string ) ) {
3892+
case 'application/ecmascript':
3893+
case 'application/javascript':
3894+
case 'application/x-ecmascript':
3895+
case 'application/x-javascript':
3896+
case 'text/ecmascript':
3897+
case 'text/javascript':
3898+
case 'text/javascript1.0':
3899+
case 'text/javascript1.1':
3900+
case 'text/javascript1.2':
3901+
case 'text/javascript1.3':
3902+
case 'text/javascript1.4':
3903+
case 'text/javascript1.5':
3904+
case 'text/jscript':
3905+
case 'text/livescript':
3906+
case 'text/x-ecmascript':
3907+
case 'text/x-javascript':
3908+
return true;
3909+
3910+
/*
3911+
* > Otherwise, if the script block's type string is an ASCII case-insensitive match for
3912+
* > the string "module", then set el's type to "module".
3913+
*
3914+
* A module is evaluated as JavaScript
3915+
*/
3916+
case 'module':
3917+
return true;
3918+
}
3919+
3920+
/*
3921+
* > - Otherwise, if the script block's type string is an ASCII case-insensitive match for
3922+
* > the string "importmap", then set el's type to "importmap".
3923+
*
3924+
* An importmap is JSON and not evaluated as JavaScript. This case is not handled here.
3925+
*/
3926+
3927+
/*
3928+
* > Otherwise, return. (No script is executed, and el's type is left as null.)
3929+
*/
3930+
return false;
3931+
}
3932+
37973933
/**
37983934
* Updates or creates a new attribute on the currently matched tag with the passed value.
37993935
*

0 commit comments

Comments
 (0)