<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[44136] trunk/tests/phpunit/tests: KSES: Allow `url()` to be used in inline CSS.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/44136">44136</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/44136","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>pento</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2018-12-14 01:40:50 +0000 (Fri, 14 Dec 2018)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>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 <a href="https://core.trac.wordpress.org/changeset/43781">[43781]</a> from the 5.0 branch to core.

Props peterwilsoncc, azaozz, pento, dd32.
Fixes <a href="https://core.trac.wordpress.org/ticket/45067">#45067</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesksesphp">trunk/src/wp-includes/kses.php</a></li>
<li><a href="#trunktestsphpunittestsksesphp">trunk/tests/phpunit/tests/kses.php</a></li>
<li><a href="#trunktestsphpunittestsshortcodephp">trunk/tests/phpunit/tests/shortcode.php</a></li>
</ul>

<h3>Property Changed</h3>
<ul>
<li><a href="#trunk">trunk/</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<span class="cx" style="display: block; padding: 0 10px">Index: trunk
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- trunk        2018-12-14 01:37:40 UTC (rev 44135)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ trunk 2018-12-14 01:40:50 UTC (rev 44136)
</ins><a id="trunk"></a>
<div class="propset"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Property changes: trunk</h4>
<pre class="diff"><span>
</span></pre></div>
<a id="svnmergeinfo"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: svn:mergeinfo</h4></div>
<span class="cx" style="display: block; padding: 0 10px"> /branches/3.3:20543
</span><span class="cx" style="display: block; padding: 0 10px"> /branches/3.4:21757
</span><span class="cx" style="display: block; padding: 0 10px"> /branches/4.9:43557,43622
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/branches/5.0:43681-43682,43684-43688,43719-43720,43723,43726-43727,43729-43731,43734-43744,43751-43754,43758,43761-43765,43767-43770,43772,43774-43780,43783
</del><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/branches/5.0:43681-43682,43684-43688,43719-43720,43723,43726-43727,43729-43731,43734-43744,43751-43754,43758,43761-43765,43767-43770,43772,43774-43781,43783
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludesksesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/kses.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/kses.php    2018-12-14 01:37:40 UTC (rev 44135)
+++ trunk/src/wp-includes/kses.php      2018-12-14 01:40:50 UTC (rev 44136)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1985,9 +1985,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        $css = wp_kses_no_null( $css );
</span><span class="cx" style="display: block; padding: 0 10px">        $css = str_replace( array( "\n", "\r", "\t" ), '', $css );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( preg_match( '%[\\\\(&=}]|/\*%', $css ) ) { // remove any inline css containing \ ( & } = or comments
-               return '';
-       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $allowed_protocols = wp_allowed_protocols();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        $css_array = explode( ';', trim( $css ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1998,6 +1996,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 4.4.0 Added support for `min-height`, `max-height`, `min-width`, and `max-width`.
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 4.6.0 Added support for `list-style-type`.
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 5.0.0 Added support for `text-transform`.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 5.0.0 Added support for `background-image`.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string[] $attr Array of allowed CSS attributes.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2006,6 +2005,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'background',
</span><span class="cx" style="display: block; padding: 0 10px">                        'background-color',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'background-image',
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        'border',
</span><span class="cx" style="display: block; padding: 0 10px">                        'border-width',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2076,6 +2076,24 @@
</span><span class="cx" style="display: block; padding: 0 10px">                )
</span><span class="cx" style="display: block; padding: 0 10px">        );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        /*
+        * 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',
+       );
+
</ins><span class="cx" style="display: block; padding: 0 10px">         if ( empty( $allowed_attr ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                return $css;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2085,20 +2103,55 @@
</span><span class="cx" style="display: block; padding: 0 10px">                if ( $css_item == '' ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        continue;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $css_item = trim( $css_item );
-               $found    = false;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               $css_item        = trim( $css_item );
+               $css_test_string = $css_item;
+               $found           = false;
+               $url_attr        = false;
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( strpos( $css_item, ':' ) === false ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $found = true;
</span><span class="cx" style="display: block; padding: 0 10px">                } else {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $parts = explode( ':', $css_item );
-                       if ( in_array( trim( $parts[0] ), $allowed_attr ) ) {
-                               $found = true;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $parts        = explode( ':', $css_item, 2 );
+                       $css_selector = trim( $parts[0] );
+
+                       if ( in_array( $css_selector, $allowed_attr, true ) ) {
+                               $found    = true;
+                               $url_attr = in_array( $css_selector, $css_url_data_types, true );
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( $found ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               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 ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         if ( $css != '' ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                $css .= ';';
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px">                         $css .= $css_item;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="trunktestsphpunittestsksesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/kses.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/kses.php        2018-12-14 01:37:40 UTC (rev 44135)
+++ trunk/tests/phpunit/tests/kses.php  2018-12-14 01:40:50 UTC (rev 44136)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -818,10 +818,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                'css'      => 'margin: 10px 20px;padding: 5px 10px',
</span><span class="cx" style="display: block; padding: 0 10px">                                'expected' => 'margin: 10px 20px;padding: 5px 10px',
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // Parenthesis ( isn't supported.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Parenthesis ( is supported for some attributes.
</ins><span class="cx" style="display: block; padding: 0 10px">                         array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'css'      => 'background: green url("foo.jpg") no-repeat fixed center',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'expected' => '',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'expected' => 'background: green url("foo.jpg") no-repeat fixed center',
</ins><span class="cx" style="display: block; padding: 0 10px">                         ),
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -920,4 +920,151 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        array( 'data**', false ),
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * 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 );',
+                               '',
+                       ),
+               );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestsshortcodephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/shortcode.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/shortcode.php   2018-12-14 01:37:40 UTC (rev 44135)
+++ trunk/tests/phpunit/tests/shortcode.php     2018-12-14 01:40:50 UTC (rev 44136)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -561,8 +561,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                '<[gallery]>',
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><span class="cx" style="display: block; padding: 0 10px">                        array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                '<div style="background:url([[gallery]])">',
-                               '<div style="background:url([[gallery]])">',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         '<div style="selector:url([[gallery]])">',
+                               '<div style="selector:url([[gallery]])">',
</ins><span class="cx" style="display: block; padding: 0 10px">                         ),
</span><span class="cx" style="display: block; padding: 0 10px">                        array(
</span><span class="cx" style="display: block; padding: 0 10px">                                '[gallery]<div>Hello</div>[/gallery]',
</span></span></pre>
</div>
</div>

</body>
</html>