Skip to content
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
c124eb5
Update wp_custom_css_cb to rely on HTML API for safe SCRIPT tag print…
sirreal Dec 17, 2025
e055156
Wrap customizer CSS test in newlines
sirreal Dec 17, 2025
33f9616
Use HTML API for style tags in script-loader
sirreal Dec 17, 2025
606539e
Use HTML Tag Processor to produce WP_Styles style tags
sirreal Dec 17, 2025
c938d4c
Use HTML Tag Processor for STYLE tags in theme.php
sirreal Dec 17, 2025
dd919f1
Build font style tags with HTML API
sirreal Dec 23, 2025
d29900a
PICKME: Update font tests to use semantic HTML comparison
sirreal Dec 23, 2025
6c6a72b
Use HTML API for hide header text
sirreal Dec 23, 2025
aad4744
Revert "Use HTML API for hide header text"
sirreal Dec 23, 2025
c3ae9a9
Merge branch 'trunk' into styles/use-html-api-for-style-tags
sirreal Dec 26, 2025
4e88745
Fix lint
sirreal Dec 26, 2025
d296d6c
Merge branch 'trunk' into styles/use-html-api-for-style-tags
sirreal Dec 29, 2025
d8a6f02
Merge branch 'styles/use-html-api-for-style-tags' into 64418/customiz…
sirreal Dec 29, 2025
67500e0
Allow arbitrary customizer custom CSS
sirreal Dec 29, 2025
01b6fb8
Fix lints
sirreal Dec 29, 2025
0141653
Restore STYLE tag trailing newline
sirreal Dec 29, 2025
6585099
Restore STYLE tag trailing newlines in theme.php
sirreal Dec 29, 2025
b0020d8
Merge branch 'styles/use-html-api-for-style-tags' into 64418/customiz…
sirreal Dec 29, 2025
407d43f
Move trailing newline out of Tag Processor
sirreal Dec 30, 2025
ffd5b45
Merge branch 'styles/use-html-api-for-style-tags' into 64418/customiz…
sirreal Dec 30, 2025
8268865
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Dec 30, 2025
9e7e04d
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 14, 2026
879c5d1
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 15, 2026
b340650
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 15, 2026
c5c8507
Update test to remove type attribute
sirreal Jan 15, 2026
6932b6c
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 15, 2026
f515c02
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 16, 2026
cbf7e21
Add covers annotations to new test
sirreal Jan 16, 2026
b13930f
Use verbose variable name for clarity
sirreal Jan 16, 2026
5a414b6
Expand explanatory comment
sirreal Jan 16, 2026
2407549
Move new test to end of file
sirreal Jan 16, 2026
ae66f56
Add test that at-property rule passes validation and is printed as de…
sirreal Jan 16, 2026
def70e2
Restore custom CSS validation, add check like global styles rest vali…
sirreal Jan 19, 2026
91adeb5
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 19, 2026
b8f92d2
Add validation tests
sirreal Jan 19, 2026
cd7712e
lint
sirreal Jan 19, 2026
10971f6
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 22, 2026
96615aa
Try restoring filter
sirreal Jan 22, 2026
a774a01
Remove empty line
sirreal Jan 22, 2026
363650d
Restore previous validate test
sirreal Jan 22, 2026
848c53b
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 23, 2026
c17f579
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 26, 2026
10d2744
Indent test nowdoc strings
sirreal Jan 26, 2026
982b467
Fix errata in comment "scripts" => "styles"
sirreal Jan 26, 2026
d703b95
Restore previous $validity->add behavior
sirreal Jan 26, 2026
eb5fdfe
Fix missing trailing newline in <style> tag test
sirreal Jan 26, 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
20 changes: 10 additions & 10 deletions src/wp-includes/class-wp-styles.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,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'>\n%s\n</style>\n",
esc_attr( $handle ),
$inline_style
);
$processor = new WP_HTML_Tag_Processor( "<style></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();
} else {
$inline_style_tag = '';
}
Expand Down Expand Up @@ -336,11 +336,11 @@ public function print_inline_style( $handle, $display = true ) {
return $output;
}

printf(
"<style id='%s-inline-css'>\n%s\n</style>\n",
esc_attr( $handle ),
$output
);
$processor = new WP_HTML_Tag_Processor( "<style></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 @@ -144,35 +144,6 @@ public function value() {
return $value;
}

/**
* Validate a received value for being valid CSS.
*
* 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.
*
* @param string $value CSS to validate.
* @return true|WP_Error True if the input was validated, otherwise WP_Error.
*/
public function validate( $value ) {
Comment thread
sirreal marked this conversation as resolved.
// Restores the more descriptive, specific name for use within this method.
$css = $value;

$validity = new WP_Error();
Comment thread
sirreal marked this conversation as resolved.

if ( preg_match( '#</?\w+#', $css ) ) {
$validity->add( 'illegal_markup', __( 'Markup is not allowed in CSS.' ) );
}

if ( ! $validity->has_errors() ) {
$validity = parent::validate( $css );
}
return $validity;
}

/**
* Store the CSS setting value in the custom_css custom post type for the stylesheet.
*
Expand Down
16 changes: 4 additions & 12 deletions src/wp-includes/fonts/class-wp-font-face.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ public function generate_and_print( array $fonts ) {
return;
}

printf( $this->get_style_element(), $css );
$processor = new WP_HTML_Tag_Processor( "<style class=\"wp-fonts-local\"></style>\n" );
$processor->next_tag();
$processor->set_modifiable_text( "\n{$css}\n" );
echo $processor->get_updated_html();
}

/**
Expand Down Expand Up @@ -193,17 +196,6 @@ private function validate_font_face_declarations( array $font_face ) {
return $font_face;
}

/**
* Gets the style element for wrapping the `@font-face` CSS.
*
* @since 6.4.0
*
* @return string The style element.
*/
private function get_style_element() {
return "<style class='wp-fonts-local'>\n%s\n</style>\n";
}

/**
* Gets the `@font-face` CSS styles for locally-hosted font files.
*
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 @@ -2413,10 +2413,12 @@ function _print_styles() {
echo "<link rel='stylesheet' href='" . esc_attr( $href ) . "' media='all' />\n";

if ( ! empty( $wp_styles->print_code ) ) {
echo "<style>\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></style>' );
Comment thread
sirreal marked this conversation as resolved.
$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();
Comment thread
sirreal marked this conversation as resolved.
Outdated
}
}

Expand Down Expand Up @@ -3146,7 +3148,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
46 changes: 30 additions & 16 deletions src/wp-includes/theme.php
Original file line number Diff line number Diff line change
Expand Up @@ -1950,11 +1950,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 @@ -1964,17 +1966,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>' );
Comment thread
sirreal marked this conversation as resolved.
$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 Expand Up @@ -2141,6 +2144,13 @@ function wp_update_custom_css_post( $css, $args = array() ) {

// Update post if it already exists, otherwise create a new one.
$post = wp_get_custom_css_post( $args['stylesheet'] );

// Remove KSES HTML filters to prevent CSS mangling.
$priority = has_filter( 'content_save_pre', 'wp_filter_post_kses' );
Comment thread
sirreal marked this conversation as resolved.
Outdated
if ( false !== $priority ) {
remove_filter( 'content_save_pre', 'wp_filter_post_kses', $priority );
}

if ( $post ) {
$post_data['ID'] = $post->ID;
$r = wp_update_post( wp_slash( $post_data ), true );
Expand All @@ -2160,6 +2170,10 @@ function wp_update_custom_css_post( $css, $args = array() ) {
}
}

if ( false !== $priority ) {
add_filter( 'content_save_pre', 'wp_filter_post_kses', $priority );
}

if ( is_wp_error( $r ) ) {
return $r;
}
Expand Down
46 changes: 21 additions & 25 deletions tests/phpunit/tests/customize/custom-css-setting.php
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,27 @@ public function test_get_custom_css_post_queries_after_failed_lookup() {
$this->assertSame( get_num_queries(), $queries_before );
}

/**
* Ensure that dangerous STYLE tag contents do not break HTML output.
*
* @ticket 64418
*/
public function test_wp_custom_css_cb_escapes_dangerous_html() {
Comment thread
sirreal marked this conversation as resolved.
Outdated
wp_update_custom_css_post(
'*::before { content: "</style><script>alert(1)</script>"; }',
array(
'stylesheet' => $this->setting->stylesheet,
)
);
$output = get_echo( 'wp_custom_css_cb' );
$expected = <<<'HTML'
<style id="wp-custom-css" type="text/css">
*::before { content: "\3c\2fstyle><script>alert(1)</script>"; }
</style>
HTML;
$this->assertEqualHTML( $expected, $output );
}

/**
* Test that wp_update_custom_css_post() updates the 'custom_css_post_id' theme mod.
*
Expand Down Expand Up @@ -373,29 +394,4 @@ public function filter_update_custom_css_data( $data, $args ) {
$data['post_title'] = 'Ignored';
return $data;
}

/**
* Tests that validation errors are caught appropriately.
*
* Note that the $validity \WP_Error object must be reset each time
* as it picks up the Errors and passes them to the next assertion.
*
* @covers WP_Customize_Custom_CSS_Setting::validate
*/
public function test_validate() {

// Empty CSS throws no errors.
$result = $this->setting->validate( '' );
$this->assertTrue( $result );

// Basic, valid CSS throws no errors.
$basic_css = 'body { background: #f00; } h1.site-title { font-size: 36px; } a:hover { text-decoration: none; } input[type="text"] { padding: 1em; }';
$result = $this->setting->validate( $basic_css );
$this->assertTrue( $result );

// Check for markup.
$unclosed_comment = $basic_css . '</style>';
$result = $this->setting->validate( $unclosed_comment );
$this->assertArrayHasKey( 'illegal_markup', $result->errors );
}
}
Loading