diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 20790d1c44..3da5bf2624 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -1076,6 +1076,7 @@ function wp_kses_attr( $element, $attr, $allowed_html, $allowed_protocols ) { * Determines whether an attribute is allowed. * * @since 4.2.3 + * @since 5.0.0 Add support for `data-*` wildcard attributes. * * @param string $name The attribute name. Passed by reference. Returns empty string when not allowed. * @param string $value The attribute value. Passed by reference. Returns a filtered value. @@ -1090,8 +1091,26 @@ function wp_kses_attr_check( &$name, &$value, &$whole, $vless, $element, $allowe $name_low = strtolower( $name ); if ( ! isset( $allowed_attr[ $name_low ] ) || '' == $allowed_attr[ $name_low ] ) { - $name = $value = $whole = ''; - return false; + /* + * Allow `data-*` attributes. + * + * When specifying `$allowed_html`, the attribute name should be set as + * `data-*` (not to be mixed with the HTML 4.0 `data` attribute, see + * https://www.w3.org/TR/html40/struct/objects.html#adef-data). + * + * Note: the attribute name should only contain `A-Za-z0-9_-` chars, + * double hyphens `--` are not accepted by WordPress. + */ + if ( strpos( $name_low, 'data-' ) === 0 && ! empty( $allowed_attr['data-*'] ) && preg_match( '/^data(?:-[a-z0-9_]+)+$/', $name_low, $match ) ) { + /* + * Add the whole attribute name to the allowed attributes and set any restrictions + * for the `data-*` attribute values for the current element. + */ + $allowed_attr[ $match[0] ] = $allowed_attr['data-*']; + } else { + $name = $value = $whole = ''; + return false; + } } if ( 'style' == $name_low ) { @@ -2091,6 +2110,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) { * Helper function to add global attributes to a tag in the allowed html list. * * @since 3.5.0 + * @since 5.0.0 Add support for `data-*` wildcard attributes. * @access private * @ignore * @@ -2099,11 +2119,12 @@ function safecss_filter_attr( $css, $deprecated = '' ) { */ function _wp_add_global_attributes( $value ) { $global_attributes = array( - 'class' => true, - 'id' => true, - 'style' => true, - 'title' => true, - 'role' => true, + 'class' => true, + 'id' => true, + 'style' => true, + 'title' => true, + 'role' => true, + 'data-*' => true, ); if ( true === $value ) { diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index 5ae0101dab..b91fe5d386 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -825,4 +825,99 @@ EOF; ), ); } + + /** + * Data attributes are globally accepted. + * + * @ticket 33121 + */ + function test_wp_kses_attr_data_attribute_is_allowed() { + $test = '
Pens and pencils
'; + $expected = '
Pens and pencils
'; + + $this->assertEquals( $expected, wp_kses_post( $test ) ); + } + + /** + * Ensure wildcard attributes block unprefixed wildcard uses. + * + * @ticket 33121 + */ + function test_wildcard_requires_hyphen_after_prefix() { + $allowed_html = array( + 'div' => array( + 'data-*' => true, + 'on-*' => true, + ), + ); + + $string = '
Malformed attributes
'; + $expected = '
Malformed attributes
'; + + $actual = wp_kses( $string, $allowed_html ); + + $this->assertSame( $expected, $actual ); + } + + /** + * Ensure wildcard allows two hyphen. + * + * @ticket 33121 + */ + function test_wildcard_allows_two_hyphens() { + $allowed_html = array( + 'div' => array( + 'data-*' => true, + ), + ); + + $string = '
Well formed attribute
'; + $expected = '
Well formed attribute
'; + + $actual = wp_kses( $string, $allowed_html ); + + $this->assertSame( $expected, $actual ); + } + + /** + * Ensure wildcard attributes only support valid prefixes. + * + * @dataProvider data_wildcard_attribute_prefixes + * + * @ticket 33121 + */ + function test_wildcard_attribute_prefixes( $wildcard_attribute, $expected ) { + $allowed_html = array( + 'div' => array( + $wildcard_attribute => true, + ), + ); + + $name = str_replace( '*', strtolower( __FUNCTION__ ), $wildcard_attribute ); + $value = __FUNCTION__; + $whole = "{$name}=\"{$value}\""; + + $actual = wp_kses_attr_check( $name, $value, $whole, 'n', 'div', $allowed_html ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @return array Array of arguments for wildcard testing + * [0] The prefix being tested. + * [1] The outcome of `wp_kses_attr_check` for the prefix. + */ + function data_wildcard_attribute_prefixes() { + return array( + // Ends correctly + array( 'data-*', true ), + + // Does not end with trialing `-`. + array( 'data*', false ), + + // Multiple wildcards. + array( 'd*ta-*', false ), + array( 'data**', false ), + ); + } } diff --git a/tests/phpunit/tests/rest-api/rest-attachments-controller.php b/tests/phpunit/tests/rest-api/rest-attachments-controller.php index 465b58bf93..70ea949a8a 100644 --- a/tests/phpunit/tests/rest-api/rest-attachments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-attachments-controller.php @@ -1079,9 +1079,9 @@ class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Control array( // Raw values. array( - 'title' => 'link', - 'description' => 'link', - 'caption' => 'link', + 'title' => 'link', + 'description' => 'link', + 'caption' => 'link', ), // Expected returned values. array( diff --git a/tests/phpunit/tests/rest-api/rest-posts-controller.php b/tests/phpunit/tests/rest-api/rest-posts-controller.php index eef8bc85dc..ebb2d507dd 100644 --- a/tests/phpunit/tests/rest-api/rest-posts-controller.php +++ b/tests/phpunit/tests/rest-api/rest-posts-controller.php @@ -3283,9 +3283,9 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te array( // Raw values. array( - 'title' => 'link', - 'content' => 'link', - 'excerpt' => 'link', + 'title' => 'link', + 'content' => 'link', + 'excerpt' => 'link', ), // Expected returned values. array(