Skip to content
Draft
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
22 changes: 10 additions & 12 deletions src/wp-includes/class-wp-styles.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,11 @@ public function do_item( $handle, $group = false ) {
$inline_style = $this->print_inline_style( $handle, false );

if ( $inline_style ) {
$inline_style_tag = sprintf(
"<style id='%s-inline-css'%s>\n%s\n</style>\n",
esc_attr( $handle ),
$this->type_attr,
$inline_style
);
$processor = new WP_HTML_Tag_Processor( "<style{$this->type_attr}></style>\n" );
$processor->next_tag();
$processor->set_attribute( 'id', "{$handle}-inline-css" );
$processor->set_modifiable_text( "\n{$inline_style}\n" );
$inline_style_tag = $processor->get_updated_html();
Copy link
Member

Choose a reason for hiding this comment

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

it might be worth calling next_token() here to verify that there are no further tokens, as we expect none. if we get one, it means that something inside of $this->type_attr broke out of the tag and potentially out of the style.

in fact, if we do this before setting the modifiable text we can ensure those attributes don’t mess with the STYLE contents

Copy link
Member

Choose a reason for hiding this comment

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

we can also leverage wp_kses_hair() (when fixed to rely on the HTML API) and transfer the attributes, but this is tantamount to my suggestion above

Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is malformed type_attr is something we need to worry about. I did a quick search to see if it is being abused, and I don't see it. If someone were to do something bad with the attribute, then this would be _doing_it_wrong. I've actually been thinking lately that we might want to eliminate the $type_attr entirely as it is obsolete now with HTML5.

Copy link
Member

Choose a reason for hiding this comment

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

thanks. I didn’t look into it any further. this appears different in nature as well from some of our other HTML5 theme-support checks: we’re not changing the markup structure, so maybe we can remove it.

there is a case where this could lead to styling change, and that’s when someone is targeting the STYLE element based on the presence of the type attribute, such as style[type~=css], which I think would match or fail to match based on the presence of this attribute.

so maybe we propose dropping it entirely and always omit the type_attr since there is no place where having it with the privately-set value of text/css would lead to different interpretations than not having it.

Copy link
Member

Choose a reason for hiding this comment

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

there is a case where this could lead to styling change, and that’s when someone is targeting the STYLE element based on the presence of the type attribute, such as style[type~=css], which I think would match or fail to match based on the presence of this attribute.

How meta 😄 I'm not aware of style rules having selectors which target other STYLE tags, but anything is possible. This seems extremely unlikely, however.

so maybe we propose dropping it entirely and always omit the type_attr since there is no place where having it with the privately-set value of text/css would lead to different interpretations than not having it.

I would endorse removing the type attribute since stylesheets are CSS by default, though this wouldn't have to be done as part of this PR. The same could be done for SCRIPT tags being JavaScript as well, but that's out of scope here. The $type_attr is a vestige of the XHTML/HTML4 past.

Copy link
Member

Choose a reason for hiding this comment

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

I'm not aware of style rules having selectors which target other STYLE tags

I guess I said styling change but was also thinking more about document.querySelector(). Either way, I think we are in agreement.

Good point about the <script> type as well, and on doing it in another change.

Copy link
Member

Choose a reason for hiding this comment

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

Tasked in Core-64428

} else {
$inline_style_tag = '';
}
Expand Down Expand Up @@ -364,12 +363,11 @@ public function print_inline_style( $handle, $display = true ) {
return $output;
}

printf(
"<style id='%s-inline-css'%s>\n%s\n</style>\n",
esc_attr( $handle ),
$this->type_attr,
$output
);
$processor = new WP_HTML_Tag_Processor( "<style{$this->type_attr}></style>\n" );
$processor->next_tag();
$processor->set_attribute( 'id', "{$handle}-inline-css" );
$processor->set_modifiable_text( "\n{$output}\n" );
echo $processor->get_updated_html();

return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,14 @@ public function value() {
}

/**
* Validate a received value for being valid CSS.
* Validate a received value for being safe HTML STYLE tag contents.
*
* Checks for imbalanced braces, brackets, and comments.
* Notifications are rendered when the customizer state is saved.
*
* @since 4.7.0
* @since 4.9.0 Checking for balanced characters has been moved client-side via linting in code editor.
* @since 5.9.0 Renamed `$css` to `$value` for PHP 8 named parameter support.
* @since 7.0.0 Relaxed to only check for safe HTML STYLE tag contents.
*
* @param string $value CSS to validate.
* @return true|WP_Error True if the input was validated, otherwise WP_Error.
Expand All @@ -163,8 +163,16 @@ public function validate( $value ) {

$validity = new WP_Error();

if ( preg_match( '#</?\w+#', $css ) ) {
$validity->add( 'illegal_markup', __( 'Markup is not allowed in CSS.' ) );
/**
* Check for a closing STYLE tag inside the CSS.
*
* STYLE tags are processed using the "generic raw text parsing algorithm." They contain
* raw text up until a matching closing tag.
*
* @see https://html.spec.whatwg.org/multipage/parsing.html#generic-raw-text-element-parsing-algorithm
*/
if ( false !== stripos( $css, '</style' ) ) {
$validity->add( 'illegal_markup', __( 'CSS must not contain possible closing STYLE tag "</style".' ) );
}

if ( ! $validity->has_errors() ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -659,16 +659,25 @@ public function get_theme_items( $request ) {
/**
* Validate style.css as valid CSS.
*
* Currently just checks for invalid markup.
* Currently just checks that CSS will not break an HTML STYLE tag.
*
* @since 6.2.0
* @since 6.4.0 Changed method visibility to protected.
* @since 7.0.0 Relaxed to only check for safe HTML STYLE tag contents.
*
* @param string $css CSS to validate.
* @return true|WP_Error True if the input was validated, otherwise WP_Error.
*/
protected function validate_custom_css( $css ) {
if ( preg_match( '#</?\w+#', $css ) ) {
/**
* Check for a closing STYLE tag inside the CSS.
*
* STYLE tags are processed using the "generic raw text parsing algorithm." They contain
* raw text up until a matching closing tag.
*
* @see https://html.spec.whatwg.org/multipage/parsing.html#generic-raw-text-element-parsing-algorithm
*/
if ( false !== stripos( $css, '</style' ) ) {
return new WP_Error(
'rest_custom_css_illegal_markup',
__( 'Markup is not allowed in CSS.' ),
Expand Down
15 changes: 10 additions & 5 deletions src/wp-includes/script-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -2417,10 +2417,12 @@ function _print_styles() {
echo "<link rel='stylesheet' href='" . esc_attr( $href ) . "'{$type_attr} media='all' />\n";

if ( ! empty( $wp_styles->print_code ) ) {
echo "<style{$type_attr}>\n";
echo $wp_styles->print_code;
echo sprintf( "\n/*# sourceURL=%s */", rawurlencode( $concat_source_url ) );
echo "\n</style>\n";
$processor = new WP_HTML_Tag_Processor( "<style{$type_attr}></style>" );
$processor->next_tag();
$style_tag_contents = "\n{$wp_styles->print_code}\n"
. sprintf( "/*# sourceURL=%s */\n", rawurlencode( $concat_source_url ) );
$processor->set_modifiable_text( $style_tag_contents );
echo $processor->get_updated_html();
}
}

Expand Down Expand Up @@ -3217,7 +3219,10 @@ function wp_enqueue_block_support_styles( $style, $priority = 10 ) {
add_action(
$action_hook_name,
static function () use ( $style ) {
echo "<style>$style</style>\n";
$processor = new WP_HTML_Tag_Processor( "<style></style>\n" );
$processor->next_tag();
$processor->set_modifiable_text( $style );
echo $processor->get_updated_html();
},
$priority
);
Expand Down
35 changes: 19 additions & 16 deletions src/wp-includes/theme.php
Original file line number Diff line number Diff line change
Expand Up @@ -1953,11 +1953,13 @@ function _custom_background_cb() {

$style .= $image . $position . $size . $repeat . $attachment;
}
?>
<style<?php echo $type_attr; ?> id="custom-background-css">
body.custom-background { <?php echo trim( $style ); ?> }
</style>
<?php

$processor = new WP_HTML_Tag_Processor( "<style{$type_attr} id=\"custom-background-css\"></style>" );
$processor->next_tag();

$style_tag_content = 'body.custom-background { ' . trim( $style ) . ' }';
$processor->set_modifiable_text( "\n{$style_tag_content}\n" );
echo $processor->get_updated_html();
}

/**
Expand All @@ -1967,17 +1969,18 @@ function _custom_background_cb() {
*/
function wp_custom_css_cb() {
$styles = wp_get_custom_css();
if ( $styles || is_customize_preview() ) :
$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
?>
<style<?php echo $type_attr; ?> id="wp-custom-css">
<?php
// Note that esc_html() cannot be used because `div &gt; span` is not interpreted properly.
echo strip_tags( $styles );
?>
</style>
<?php
endif;
if ( ! $styles && ! is_customize_preview() ) {
return;
}

$processor = new WP_HTML_Tag_Processor( '<style></style>' );
$processor->next_tag();
if ( ! current_theme_supports( 'html5', 'style' ) ) {
$processor->set_attribute( 'type', 'text/css' );
}
$processor->set_attribute( 'id', 'wp-custom-css' );
$processor->set_modifiable_text( "\n{$styles}\n" );
echo $processor->get_updated_html();
}

/**
Expand Down
33 changes: 32 additions & 1 deletion tests/phpunit/tests/rest-api/rest-global-styles-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,7 @@ public function test_update_item_valid_styles_css() {
/**
* @covers WP_REST_Global_Styles_Controller::update_item
* @ticket 57536
* @ticket 64418
*/
public function test_update_item_invalid_styles_css() {
wp_set_current_user( self::$admin_id );
Expand All @@ -659,7 +660,9 @@ public function test_update_item_invalid_styles_css() {
$request = new WP_REST_Request( 'PUT', '/wp/v2/global-styles/' . self::$global_styles_id );
$request->set_body_params(
array(
'styles' => array( 'css' => '<p>test</p> body { color: red; }' ),
'styles' => array(
'css' => '</style would close the containing element and is not allowed.',
),
)
);
$response = rest_get_server()->dispatch( $request );
Expand Down Expand Up @@ -826,4 +829,32 @@ public function test_global_styles_route_args_schema() {
$this->assertArrayHasKey( 'type', $route_data[0]['args']['id'] );
$this->assertSame( 'integer', $route_data[0]['args']['id']['type'] );
}

/**
* @covers WP_REST_Global_Styles_Controller::update_item
* @ticket 64418
*/
public function test_update_allows_valid_css_with_more_syntax() {
wp_set_current_user( self::$admin_id );
if ( is_multisite() ) {
grant_super_admin( self::$admin_id );
}
$request = new WP_REST_Request( 'PUT', '/wp/v2/global-styles/' . self::$global_styles_id );
$css = <<<'CSS'
@property --animate {
syntax: "<custom-ident>";
inherits: true;
initial-value: false;
}
CSS;
$request->set_body_params(
array(
'styles' => array( 'css' => $css ),
)
);

$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$this->assertSame( $css, $data['styles']['css'] );
}
}
Loading