KSES: Add support for rgb(a) color#10923
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
3e19b4e to
797ab0e
Compare
Co-authored-by: Weston Ruter <westonruter@gmail.com>
Co-authored-by: Weston Ruter <westonruter@gmail.com>
| 'cursor', | ||
| 'direction', | ||
| 'float', | ||
| 'list-style-type', | ||
| 'object-fit', | ||
| 'object-position', | ||
| 'opacity', | ||
| 'overflow', | ||
| 'vertical-align', | ||
| 'writing-mode', | ||
|
|
||
| 'position', | ||
| 'top', | ||
| 'right', | ||
| 'bottom', | ||
| 'left', | ||
| 'z-index', | ||
| 'box-shadow', | ||
| 'aspect-ratio', | ||
| 'container-type', | ||
|
|
||
| // Custom CSS properties. | ||
| '--*', | ||
| ) | ||
| ); | ||
|
|
||
| /* | ||
| * CSS attributes that accept URL data types. | ||
| * | ||
| * This is in accordance to the CSS spec and unrelated to | ||
| * the sub-set of supported attributes above. | ||
| * | ||
| * See: https://developer.mozilla.org/en-US/docs/Web/CSS/url | ||
| */ | ||
| $css_url_data_types = array( | ||
| 'background', | ||
| 'background-image', | ||
|
|
||
| 'cursor', | ||
| 'filter', | ||
|
|
||
| 'list-style', | ||
| 'list-style-image', | ||
| ); | ||
|
|
||
| /* | ||
| * CSS attributes that accept rgb(a) color data types. | ||
| */ | ||
| $css_color_data_types = array( | ||
| 'color', | ||
|
|
||
| 'border', | ||
| 'border-color', | ||
| 'border-right', | ||
| 'border-right-color', | ||
| 'border-bottom', | ||
| 'border-bottom-color', | ||
| 'border-left', | ||
| 'border-left-color', | ||
| 'border-top', | ||
| 'border-top-color', | ||
|
|
||
| 'background', | ||
| 'background-color', | ||
| ); | ||
|
|
||
| /* | ||
| * CSS attributes that accept gradient data types. | ||
| * | ||
| */ | ||
| $css_gradient_data_types = array( | ||
| 'background', | ||
| 'background-image', | ||
| ); | ||
|
|
||
| if ( empty( $allowed_attr ) ) { | ||
| return $css; | ||
| } | ||
|
|
||
| $css = ''; | ||
| foreach ( $css_array as $css_item ) { | ||
| if ( '' === $css_item ) { | ||
| continue; | ||
| } | ||
|
|
||
| $css_item = trim( $css_item ); | ||
| $css_test_string = $css_item; | ||
| $found = false; | ||
| $url_attr = false; | ||
| $color_attr = false; | ||
| $gradient_attr = false; | ||
| $is_custom_var = false; | ||
|
|
||
| if ( ! str_contains( $css_item, ':' ) ) { | ||
| $found = true; | ||
| } else { | ||
| $parts = explode( ':', $css_item, 2 ); | ||
| $css_selector = trim( $parts[0] ); | ||
|
|
||
| // Allow assigning values to CSS variables. | ||
| if ( in_array( '--*', $allowed_attr, true ) && preg_match( '/^--[a-zA-Z0-9-_]+$/', $css_selector ) ) { | ||
| $allowed_attr[] = $css_selector; | ||
| $is_custom_var = true; | ||
| } | ||
|
|
||
| if ( in_array( $css_selector, $allowed_attr, true ) ) { | ||
| $found = true; | ||
| $url_attr = in_array( $css_selector, $css_url_data_types, true ); | ||
| $gradient_attr = in_array( $css_selector, $css_gradient_data_types, true ); | ||
| $color_attr = in_array( $css_selector, $css_color_data_types, true ); | ||
| } | ||
|
|
||
| if ( $is_custom_var ) { | ||
| $css_value = trim( $parts[1] ); | ||
| $url_attr = str_starts_with( $css_value, 'url(' ); | ||
| $gradient_attr = str_contains( $css_value, '-gradient(' ); | ||
| } | ||
| } | ||
|
|
||
| if ( $found && $url_attr ) { | ||
| // Simplified: matches the sequence `url(*)`. | ||
| preg_match_all( '/url\([^)]+\)/', $parts[1], $url_matches ); | ||
|
|
||
| foreach ( $url_matches[0] as $url_match ) { | ||
| // Clean up the URL from each of the matches above. | ||
| preg_match( '/^url\(\s*([\'\"]?)(.*)(\g1)\s*\)$/', $url_match, $url_pieces ); | ||
|
|
||
| if ( empty( $url_pieces[2] ) ) { | ||
| $found = false; | ||
| break; | ||
| } | ||
|
|
||
| $url = trim( $url_pieces[2] ); | ||
|
|
||
| if ( empty( $url ) || wp_kses_bad_protocol( $url, $allowed_protocols ) !== $url ) { | ||
| $found = false; | ||
| break; | ||
| } else { | ||
| // Remove the whole `url(*)` bit that was matched above from the CSS. | ||
| $css_test_string = str_replace( $url_match, '', $css_test_string ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if ( $found && $gradient_attr ) { | ||
| $css_value = trim( $parts[1] ); | ||
| if ( preg_match( '/^(repeating-)?(linear|radial|conic)-gradient\(([^()]|rgb[a]?\([^()]*\))*\)$/', $css_value ) ) { | ||
| // Remove the whole `gradient` bit that was matched above from the CSS. | ||
| $css_test_string = str_replace( $css_value, '', $css_test_string ); | ||
| } | ||
| } | ||
|
|
||
| if ( $found && $color_attr ) { | ||
| $css_value = trim( $parts[1] ); | ||
| $comma_syntax = '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i'; | ||
| $space_syntax = '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i'; | ||
|
|
||
| if ( preg_match( $comma_syntax, $css_value ) || preg_match( $space_syntax, $css_value ) ) { | ||
| $css_test_string = str_replace( $css_value, '', $css_test_string ); | ||
| } | ||
| } | ||
|
|
||
| if ( $found ) { | ||
| /* | ||
| * Allow CSS functions like var(), calc(), etc. by removing them from the test string. |
There was a problem hiding this comment.
Note that border and background won't work in cases like:
border: 1px solid rgb(0,0,0)background: rgb(0,0,0) center
Because the regex has a below has ^ and $ anchors.
These anchors could be removed to match url() handling:
wordpress-develop/src/wp-includes/kses.php
Line 2835 in e8eff13
(Props Claude Code Opus 4.7 for insight.)
| array( | ||
| 'css' => 'color: rgb(255, 50%, 0)', | ||
| 'expected' => 'color: rgb(255, 50%, 0)', | ||
| ), |
There was a problem hiding this comment.
Thanks for noticing, I have corrected the invalid values in 129c006
Drop the `^`/`$` anchors on the rgb()/rgba() match in `safecss_filter_attr()` and switch to `preg_match_all`, mirroring how `url()` is handled. This lets shorthand values like `border: 1px solid rgb(0, 0, 0)` and `background: rgb(0, 0, 0) center` pass sanitization, while malformed colors still fail because the leftover `(` is rejected downstream. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`rgb(255, 50%, 0)` mixes numbers and percentages within the legacy comma-separated syntax, which is invalid per CSS Color Level 4 — only the modern space-separated syntax permits mixing. Switch the case to `rgb(255 50% 0)`. Also bring `rgba(100, 200, 300, 0.8)` into the 0–255 range (`250`) so the fixtures reflect realistic, valid colors rather than relying on browser clamping. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Note
This PR is the same as #3097. I submitted this new PR because I accidentally closed #3097 and it can no longer be reopened.
Trac ticket: https://core.trac.wordpress.org/ticket/56391
Related gutenberg issue: WordPress/gutenberg#39402
Related gutenberg PR: WordPress/gutenberg#39488
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.