KSES: Allow `url()` to be used in inline CSS.

The cover image block uses the `url()` function in its inline CSS, to show the cover image. KSES didn't allow this, causing the block to not save correctly for Author and Contributor users. As KSES does already check each attribute name against an allowed list, we're able to add an extra check for certain attributes to be able to use the `url()` function, too.

Merges [43781] from the 5.0 branch to core.

Props peterwilsoncc, azaozz, pento, dd32.
Fixes #45067.



git-svn-id: https://develop.svn.wordpress.org/trunk@44136 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Gary Pendergast 2018-12-14 01:40:50 +00:00
parent c125a50838
commit 79486d7656
3 changed files with 213 additions and 13 deletions

View File

@ -1985,9 +1985,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
$css = wp_kses_no_null( $css ); $css = wp_kses_no_null( $css );
$css = str_replace( array( "\n", "\r", "\t" ), '', $css ); $css = str_replace( array( "\n", "\r", "\t" ), '', $css );
if ( preg_match( '%[\\\\(&=}]|/\*%', $css ) ) { // remove any inline css containing \ ( & } = or comments $allowed_protocols = wp_allowed_protocols();
return '';
}
$css_array = explode( ';', trim( $css ) ); $css_array = explode( ';', trim( $css ) );
@ -1998,6 +1996,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
* @since 4.4.0 Added support for `min-height`, `max-height`, `min-width`, and `max-width`. * @since 4.4.0 Added support for `min-height`, `max-height`, `min-width`, and `max-width`.
* @since 4.6.0 Added support for `list-style-type`. * @since 4.6.0 Added support for `list-style-type`.
* @since 5.0.0 Added support for `text-transform`. * @since 5.0.0 Added support for `text-transform`.
* @since 5.0.0 Added support for `background-image`.
* *
* @param string[] $attr Array of allowed CSS attributes. * @param string[] $attr Array of allowed CSS attributes.
*/ */
@ -2006,6 +2005,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
array( array(
'background', 'background',
'background-color', 'background-color',
'background-image',
'border', 'border',
'border-width', 'border-width',
@ -2076,6 +2076,24 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
) )
); );
/*
* 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',
'list-style',
'list-style-image',
);
if ( empty( $allowed_attr ) ) { if ( empty( $allowed_attr ) ) {
return $css; return $css;
} }
@ -2085,20 +2103,55 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
if ( $css_item == '' ) { if ( $css_item == '' ) {
continue; continue;
} }
$css_item = trim( $css_item );
$found = false; $css_item = trim( $css_item );
$css_test_string = $css_item;
$found = false;
$url_attr = false;
if ( strpos( $css_item, ':' ) === false ) { if ( strpos( $css_item, ':' ) === false ) {
$found = true; $found = true;
} else { } else {
$parts = explode( ':', $css_item ); $parts = explode( ':', $css_item, 2 );
if ( in_array( trim( $parts[0] ), $allowed_attr ) ) { $css_selector = trim( $parts[0] );
$found = true;
if ( in_array( $css_selector, $allowed_attr, true ) ) {
$found = true;
$url_attr = in_array( $css_selector, $css_url_data_types, true );
} }
} }
if ( $found ) {
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 ) || $url !== wp_kses_bad_protocol( $url, $allowed_protocols ) ) {
$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 );
}
}
}
// Remove any CSS containing containing \ ( & } = or comments, except for url() useage checked above.
if ( $found && ! preg_match( '%[\\\(&=}]|/\*%', $css_test_string ) ) {
if ( $css != '' ) { if ( $css != '' ) {
$css .= ';'; $css .= ';';
} }
$css .= $css_item; $css .= $css_item;
} }
} }

View File

@ -818,10 +818,10 @@ EOF;
'css' => 'margin: 10px 20px;padding: 5px 10px', 'css' => 'margin: 10px 20px;padding: 5px 10px',
'expected' => 'margin: 10px 20px;padding: 5px 10px', 'expected' => 'margin: 10px 20px;padding: 5px 10px',
), ),
// Parenthesis ( isn't supported. // Parenthesis ( is supported for some attributes.
array( array(
'css' => 'background: green url("foo.jpg") no-repeat fixed center', 'css' => 'background: green url("foo.jpg") no-repeat fixed center',
'expected' => '', 'expected' => 'background: green url("foo.jpg") no-repeat fixed center',
), ),
); );
} }
@ -920,4 +920,151 @@ EOF;
array( 'data**', false ), array( 'data**', false ),
); );
} }
/**
* Test URL sanitization in the style tag.
*
* @dataProvider data_kses_style_attr_with_url
*
* @ticket 45067
*
* @param $input string The style attribute saved in the editor.
* @param $expected string The sanitized style attribute.
*/
function test_kses_style_attr_with_url( $input, $expected ) {
$actual = safecss_filter_attr( $input );
$this->assertSame( $expected, $actual );
}
/**
* Data provider testing style attribute sanitization.
*
* @return array Nested array of input, expected pairs.
*/
function data_kses_style_attr_with_url() {
return array(
/*
* Valid use cases.
*/
// Double quotes.
array(
'background-image: url( "http://example.com/valid.gif" );',
'background-image: url( "http://example.com/valid.gif" )',
),
// Single quotes.
array(
"background-image: url( 'http://example.com/valid.gif' );",
"background-image: url( 'http://example.com/valid.gif' )",
),
// No quotes.
array(
'background-image: url( http://example.com/valid.gif );',
'background-image: url( http://example.com/valid.gif )',
),
// Single quotes, extra spaces.
array(
"background-image: url( ' http://example.com/valid.gif ' );",
"background-image: url( ' http://example.com/valid.gif ' )",
),
// Line breaks, single quotes.
array(
"background-image: url(\n'http://example.com/valid.gif' );",
"background-image: url('http://example.com/valid.gif' )",
),
// Tabs not spaces, single quotes.
array(
"background-image: url(\t'http://example.com/valid.gif'\t\t);",
"background-image: url('http://example.com/valid.gif')",
),
// Single quotes, absolute path.
array(
"background: url('/valid.gif');",
"background: url('/valid.gif')",
),
// Single quotes, relative path.
array(
"background: url('../wp-content/uploads/2018/10/valid.gif');",
"background: url('../wp-content/uploads/2018/10/valid.gif')",
),
// Error check: valid property not containing a URL.
array(
'background: red',
'background: red',
),
/*
* Invalid use cases.
*/
// Attribute doesn't support URL properties.
array(
'color: url( "http://example.com/invalid.gif" );',
'',
),
// Mismatched quotes.
array(
'background-image: url( "http://example.com/valid.gif\' );',
'',
),
// Bad protocol, double quotes.
array(
'background-image: url( "bad://example.com/invalid.gif" );',
'',
),
// Bad protocol, single quotes.
array(
"background-image: url( 'bad://example.com/invalid.gif' );",
'',
),
// Bad protocol, single quotes.
array(
"background-image: url( 'bad://example.com/invalid.gif' );",
'',
),
// Bad protocol, single quotes, strange spacing.
array(
"background-image: url( ' \tbad://example.com/invalid.gif ' );",
'',
),
// Bad protocol, no quotes.
array(
'background-image: url( bad://example.com/invalid.gif );',
'',
),
// No URL inside url().
array(
'background-image: url();',
'',
),
// Malformed, no closing `)`.
array(
'background-image: url( "http://example.com" ;',
'',
),
// Malformed, no closing `"`.
array(
'background-image: url( "http://example.com );',
'',
),
);
}
} }

View File

@ -561,8 +561,8 @@ EOF;
'<[gallery]>', '<[gallery]>',
), ),
array( array(
'<div style="background:url([[gallery]])">', '<div style="selector:url([[gallery]])">',
'<div style="background:url([[gallery]])">', '<div style="selector:url([[gallery]])">',
), ),
array( array(
'[gallery]<div>Hello</div>[/gallery]', '[gallery]<div>Hello</div>[/gallery]',