assertSame( $expect_string, wp_kses( $string, $allowedposttags ) );
}
/**
* Data provider for test_wp_filter_post_kses_address.
*
* @return array[] Arguments {
* @type string $string Test string for kses.
* @type string $expect_string Expected result after passing through kses.
* }
*/
function data_wp_filter_post_kses_address() {
$attributes = array(
'class' => 'classname',
'id' => 'id',
'style' => array(
'color: red;',
'color: red',
'color: red; text-align:center',
'color: red; text-align:center;',
),
'title' => 'title',
);
$data = array();
foreach ( $attributes as $name => $values ) {
foreach ( (array) $values as $value ) {
$string = "
1 WordPress Avenue, The Internet.";
$expect_string = "1 WordPress Avenue, The Internet.";
$data[] = array( $string, $expect_string );
}
}
return $data;
}
/**
* @dataProvider data_wp_filter_post_kses_a
* @ticket 20210
*
* @param string $string Test string for kses.
* @param string $expect_string Expected result after passing through kses.
* @return void
*/
function test_wp_filter_post_kses_a( $string, $expect_string ) {
global $allowedposttags;
$this->assertSame( $expect_string, wp_kses( $string, $allowedposttags ) );
}
/**
* Data provider for test_wp_filter_post_kses_a.
*
* @return array[] Arguments {
* @type string $string Test string for kses.
* @type string $expect_string Expected result after passing through kses.
* }
*/
function data_wp_filter_post_kses_a() {
$attributes = array(
'class' => 'classname',
'id' => 'id',
'style' => 'color: red;',
'title' => 'title',
'href' => 'http://example.com',
'rel' => 'related',
'rev' => 'revision',
'name' => 'name',
'target' => '_blank',
'download' => '',
);
$data = array();
foreach ( $attributes as $name => $value ) {
if ( $value ) {
$attr = "$name='$value'";
$expected_attr = "$name='" . trim( $value, ';' ) . "'";
} else {
$attr = $name;
$expected_attr = $name;
}
$string = "I link this";
$expect_string = "I link this";
$data[] = array( $string, $expect_string );
}
return $data;
}
/**
* Test video tag.
*
* @ticket 50167
* @ticket 29826
* @dataProvider data_wp_kses_video
*
* @param string $source Source HTML.
* @param string $context Context to use for parsing source.
* @param string $expected Expected output following KSES parsing.
*/
function test_wp_kses_video( $source, $context, $expected ) {
$actual = wp_kses( $source, $context );
$this->assertSame( $expected, $actual );
}
/**
* Data provider for test_wp_kses_video
*
* @return array[] Array containing test data {
* @type string $source Source HTML.
* @type string $context Context to use for parsing source.
* @type string $expected Expected output following KSES parsing.
* }
*/
function data_wp_kses_video() {
return array(
// Set 0: Valid post object params in post context.
array(
'',
'post',
'',
),
// Set 1: Valid post object params in data context.
array(
'',
'data',
'',
),
// Set 2: Disallowed urls in post context.
array(
'',
'post',
'',
),
// Set 3: Disallowed attributes in post context.
array(
'',
'post',
'',
),
);
}
/**
* @dataProvider data_wp_filter_post_kses_abbr
* @ticket 20210
*
* @param string $string Test string for kses.
* @param string $expect_string Expected result after passing through kses.
* @return void
*/
function test_wp_filter_post_kses_abbr( $string, $expect_string ) {
global $allowedposttags;
$this->assertSame( $expect_string, wp_kses( $string, $allowedposttags ) );
}
/**
* Data provider for data_wp_filter_post_kses_abbr.
*
* @return array[] Arguments {
* @type string $string Test string for kses.
* @type string $expect_string Expected result after passing through kses.
* }
*/
function data_wp_filter_post_kses_abbr() {
$attributes = array(
'class' => 'classname',
'id' => 'id',
'style' => 'color: red;',
'title' => 'title',
);
$data = array();
foreach ( $attributes as $name => $value ) {
$string = "WP";
$expect_string = "WP";
$data[] = array( $string, $expect_string );
}
return $data;
}
function test_feed_links() {
global $allowedposttags;
$content = <<CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
EOF;
$expected = <<CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
CLICK ME
EOF;
$this->assertSame( $expected, wp_kses( $content, $allowedposttags ) );
}
function test_wp_kses_bad_protocol() {
$bad = array(
'dummy:alert(1)',
'javascript:alert(1)',
'JaVaScRiPt:alert(1)',
'javascript:alert(1);',
'javascript:alert(1);',
'javascript:alert(1);',
'javascript:alert(1);',
'javascript:alert(1);',
'javascript:alert(1);',
'javascript:alert(1);',
'javascript:alert(1);',
'javascript:alert(1);',
'javascript:alert('XSS')',
'jav ascript:alert(1);',
'jav ascript:alert(1);',
'jav
ascript:alert(1);',
'jav
ascript:alert(1);',
' javascript:alert(1);',
'javascript:javascript:alert(1);',
'javascript:javascript:alert(1);',
'javascript:javascript:alert(1);',
'javascript:javascript:alert(1);',
'javascript:javascript:alert(1);',
'javascript:alert(1)//?:',
'feed:javascript:alert(1)',
'feed:javascript:feed:javascript:feed:javascript:alert(1)',
'javascript:alert(1)',
'javascript:x=1;alert(1)',
);
foreach ( $bad as $k => $x ) {
$result = wp_kses_bad_protocol( wp_kses_normalize_entities( $x ), wp_allowed_protocols() );
if ( ! empty( $result ) && 'alert(1);' !== $result && 'alert(1)' !== $result ) {
switch ( $k ) {
case 6:
$this->assertSame( 'javascript:alert(1);', $result );
break;
case 12:
$this->assertSame( str_replace( '&', '&', $x ), $result );
break;
case 22:
$this->assertSame( 'javascript:alert(1);', $result );
break;
case 23:
$this->assertSame( 'javascript:alert(1)//?:', $result );
break;
case 24:
$this->assertSame( 'feed:alert(1)', $result );
break;
case 26:
$this->assertSame( 'javascript:alert(1)', $result );
break;
case 27:
$this->assertSame( 'javascript:x=1;alert(1)', $result );
break;
default:
$this->fail( "wp_kses_bad_protocol failed on $k, $x. Result: $result" );
}
}
}
$bad_not_normalized = array(
'dummy:alert(1)',
'javascript:alert(1)',
'javascript&CoLon;alert(1)',
'javascript&COLON;alert(1);',
'javascript:alert(1);',
'javascript:alert(1);',
'javascript:alert(1);',
'jav ascript&COLON;alert(1);',
'javascript:javascript:alert(1);',
'javascript:javascript:alert(1);',
'javascript:javascript:alert(1);',
'javascript:javascript:alert(1);',
'javascript:alert(1)',
);
foreach ( $bad_not_normalized as $k => $x ) {
$result = wp_kses_bad_protocol( $x, wp_allowed_protocols() );
if ( ! empty( $result ) && 'alert(1);' !== $result && 'alert(1)' !== $result ) {
$this->fail( "wp_kses_bad_protocol failed on $k, $x. Result: $result" );
}
}
$safe = array(
'dummy:alert(1)',
'HTTP://example.org/',
'http://example.org/',
'http://example.org/',
'http://example.org/',
'https://example.org',
'http://example.org/wp-admin/post.php?post=2&action=edit',
'http://example.org/index.php?test='blah'',
);
foreach ( $safe as $x ) {
$result = wp_kses_bad_protocol( wp_kses_normalize_entities( $x ), array( 'http', 'https', 'dummy' ) );
if ( $result !== $x && 'http://example.org/' !== $result ) {
$this->fail( "wp_kses_bad_protocol incorrectly blocked $x" );
}
}
}
public function test_hackers_attacks() {
$xss = simplexml_load_file( DIR_TESTDATA . '/formatting/xssAttacks.xml' );
foreach ( $xss->attack as $attack ) {
if ( in_array( (string) $attack->name, array( 'IMG Embedded commands 2', 'US-ASCII encoding', 'OBJECT w/Flash 2', 'Character Encoding Example' ), true ) ) {
continue;
}
$code = (string) $attack->code;
if ( 'See Below' === $code ) {
continue;
}
if ( substr( $code, 0, 4 ) === 'perl' ) {
$pos = strpos( $code, '"' ) + 1;
$code = substr( $code, $pos, strrpos( $code, '"' ) - $pos );
$code = str_replace( '\0', "\0", $code );
}
$result = trim( wp_kses_data( $code ) );
if ( in_array( $result, array( '', 'XSS', 'alert("XSS");', "alert('XSS');" ), true ) ) {
continue;
}
switch ( $attack->name ) {
case 'XSS Locator':
$this->assertSame( '\';alert(String.fromCharCode(88,83,83))//\\\';alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//\\";alert(String.fromCharCode(88,83,83))//-->">\'>alert(String.fromCharCode(88,83,83))=&{}', $result );
break;
case 'XSS Quick Test':
$this->assertSame( '\'\';!--"=&{()}', $result );
break;
case 'SCRIPT w/Alert()':
$this->assertSame( "alert('XSS')", $result );
break;
case 'SCRIPT w/Char Code':
$this->assertSame( 'alert(String.fromCharCode(88,83,83))', $result );
break;
case 'IMG STYLE w/expression':
$this->assertSame( 'exp/*', $result );
break;
case 'List-style-image':
$this->assertSame( 'li {list-style-image: url("javascript:alert(\'XSS\')");}XSS', $result );
break;
case 'STYLE':
$this->assertSame( "alert('XSS');", $result );
break;
case 'STYLE w/background-image':
$this->assertSame( '.XSS{background-image:url("javascript:alert(\'XSS\')");}', $result );
break;
case 'STYLE w/background':
$this->assertSame( 'BODY{background:url("javascript:alert(\'XSS\')")}', $result );
break;
case 'Remote Stylesheet 2':
$this->assertSame( "@import'http://ha.ckers.org/xss.css';", $result );
break;
case 'Remote Stylesheet 3':
$this->assertSame( '<META HTTP-EQUIV="Link" Content="; REL=stylesheet">', $result );
break;
case 'Remote Stylesheet 4':
$this->assertSame( 'BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}', $result );
break;
case 'XML data island w/CDATA':
$this->assertSame( '<![CDATA[]]>', $result );
break;
case 'XML data island w/comment':
$this->assertSame( "<IMG SRC="javascript:alert('XSS')\">", $result );
break;
case 'XML HTML+TIME':
$this->assertSame( '<t:set attributeName="innerHTML" to="XSSalert(\'XSS\')">', $result );
break;
case 'Commented-out Block':
$this->assertSame( "\nalert('XSS');", $result );
break;
case 'Cookie Manipulation':
$this->assertSame( '<META HTTP-EQUIV="Set-Cookie" Content="USERID=alert(\'XSS\')">', $result );
break;
case 'SSI':
$this->assertSame( '<!--#exec cmd="/bin/echo '', $result );
break;
case 'PHP':
$this->assertSame( '<? echo('alert("XSS")\'); ?>', $result );
break;
case 'UTF-7 Encoding':
$this->assertSame( '+ADw-SCRIPT+AD4-alert(\'XSS\');+ADw-/SCRIPT+AD4-', $result );
break;
case 'Escaping JavaScript escapes':
$this->assertSame( '\";alert(\'XSS\');//', $result );
break;
case 'STYLE w/broken up JavaScript':
$this->assertSame( '@im\port\'\ja\vasc\ript:alert("XSS")\';', $result );
break;
case 'Null Chars 2':
$this->assertSame( '&alert("XSS")', $result );
break;
case 'No Closing Script Tag':
$this->assertSame( '<SCRIPT SRC=http://ha.ckers.org/xss.js', $result );
break;
case 'Half-Open HTML/JavaScript':
$this->assertSame( '<IMG SRC="javascript:alert('XSS')"', $result );
break;
case 'Double open angle brackets':
$this->assertSame( '<IFRAME SRC=http://ha.ckers.org/scriptlet.html <', $result );
break;
case 'Extraneous Open Brackets':
$this->assertSame( '<alert("XSS");//<', $result );
break;
case 'Malformed IMG Tags':
$this->assertSame( 'alert("XSS")">', $result );
break;
case 'No Quotes/Semicolons':
$this->assertSame( "a=/XSS/\nalert(a.source)", $result );
break;
case 'Evade Regex Filter 1':
$this->assertSame( '" SRC="http://ha.ckers.org/xss.js">', $result );
break;
case 'Evade Regex Filter 4':
$this->assertSame( '\'" SRC="http://ha.ckers.org/xss.js">', $result );
break;
case 'Evade Regex Filter 5':
$this->assertSame( '` SRC="http://ha.ckers.org/xss.js">', $result );
break;
case 'Filter Evasion 1':
$this->assertSame( 'document.write("<SCRI");PT SRC="http://ha.ckers.org/xss.js">', $result );
break;
case 'Filter Evasion 2':
$this->assertSame( '\'>" SRC="http://ha.ckers.org/xss.js">', $result );
break;
default:
$this->fail( 'KSES failed on ' . $attack->name . ': ' . $result );
}
}
}
function _wp_kses_allowed_html_filter( $html, $context ) {
if ( 'post' === $context ) {
return array( 'a' => array( 'href' => true ) );
} else {
return array( 'a' => array( 'href' => false ) );
}
}
/**
* @ticket 20210
*/
public function test_wp_kses_allowed_html() {
global $allowedposttags, $allowedtags, $allowedentitynames;
$this->assertSame( $allowedposttags, wp_kses_allowed_html( 'post' ) );
$tags = wp_kses_allowed_html( 'post' );
foreach ( $tags as $tag ) {
$this->assertTrue( $tag['class'] );
$this->assertTrue( $tag['id'] );
$this->assertTrue( $tag['style'] );
$this->assertTrue( $tag['title'] );
}
$this->assertSame( $allowedtags, wp_kses_allowed_html( 'data' ) );
$this->assertSame( $allowedtags, wp_kses_allowed_html( '' ) );
$this->assertSame( $allowedtags, wp_kses_allowed_html() );
$tags = wp_kses_allowed_html( 'user_description' );
$this->assertTrue( $tags['a']['rel'] );
$tags = wp_kses_allowed_html();
$this->assertFalse( isset( $tags['a']['rel'] ) );
$this->assertSame( array(), wp_kses_allowed_html( 'strip' ) );
$custom_tags = array(
'a' => array(
'href' => true,
'rel' => true,
'rev' => true,
'name' => true,
'target' => true,
),
);
$this->assertSame( $custom_tags, wp_kses_allowed_html( $custom_tags ) );
add_filter( 'wp_kses_allowed_html', array( $this, '_wp_kses_allowed_html_filter' ), 10, 2 );
$this->assertSame( array( 'a' => array( 'href' => true ) ), wp_kses_allowed_html( 'post' ) );
$this->assertSame( array( 'a' => array( 'href' => false ) ), wp_kses_allowed_html( 'data' ) );
remove_filter( 'wp_kses_allowed_html', array( $this, '_wp_kses_allowed_html_filter' ) );
$this->assertSame( $allowedposttags, wp_kses_allowed_html( 'post' ) );
$this->assertSame( $allowedtags, wp_kses_allowed_html( 'data' ) );
}
function test_hyphenated_tag() {
$string = 'Alot of hyphens.';
$custom_tags = array(
'hyphenated-tag' => array(
'attribute' => true,
),
);
$expect_stripped_string = 'Alot of hyphens.';
$expect_valid_string = 'Alot of hyphens.';
$this->assertSame( $expect_stripped_string, wp_kses_post( $string ) );
$this->assertSame( $expect_valid_string, wp_kses( $string, $custom_tags ) );
}
/**
* @ticket 26290
*/
public function test_wp_kses_normalize_entities() {
$this->assertSame( '♠', wp_kses_normalize_entities( '♠' ) );
$this->assertSame( '¹', wp_kses_normalize_entities( '¹' ) );
$this->assertSame( '²', wp_kses_normalize_entities( '²' ) );
$this->assertSame( '³', wp_kses_normalize_entities( '³' ) );
$this->assertSame( '¼', wp_kses_normalize_entities( '¼' ) );
$this->assertSame( '½', wp_kses_normalize_entities( '½' ) );
$this->assertSame( '¾', wp_kses_normalize_entities( '¾' ) );
$this->assertSame( '∴', wp_kses_normalize_entities( '∴' ) );
}
/**
* Test removal of invalid binary data for HTML.
*
* @ticket 28506
* @dataProvider data_ctrl_removal
*/
function test_ctrl_removal( $input, $output ) {
global $allowedposttags;
return $this->assertSame( $output, wp_kses( $input, $allowedposttags ) );
}
function data_ctrl_removal() {
return array(
array(
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\X1C\x1D\x1E\x1F",
'',
),
array(
"\x00h\x01e\x02l\x03l\x04o\x05 \x06w\x07o\x08r\x0Bl\x0Cd\x0E.\x0F \x10W\x11O\x12R\x13D\x14P\x15R\x16E\x17S\x18S\x19 \x1AK\x1BS\X1CE\x1DS\x1E.\x1F/",
'hello world. WORDPRESS KSES./',
),
array(
"\x1F\x1E\x1D\x1C\x1B\x1A\x19\x18\x17\x16\x15\x14\x13\x12\x11\x10\x0F\x0E\x0C\x0B\x08\x07\x06\x05\x04\X03\x02\x01\x00",
'',
),
array(
"\x1Fh\x1Ee\x1Dl\x1Cl\x1Bo\x1A \x19w\x18o\x17r\x16l\x15d\x14.\x13 \x12W\x11O\x10R\x0FD\x0EP\x0CR\x0BE\x08S\x07S\x06 \x05K\x04S\X03E\x02S\x01.\x00/",
'hello world. WORDPRESS KSES./',
),
array(
"\t\r\n word \n\r\t",
"\t\r\n word \n\r\t",
),
);
}
/**
* Test removal of '\0' strings.
*
* @ticket 28699
* @dataProvider data_slash_zero_removal
*/
function test_slash_zero_removal( $input, $output ) {
global $allowedposttags;
return $this->assertSame( $output, wp_kses( $input, $allowedposttags ) );
}
function data_slash_zero_removal() {
return array(
array(
'This \\0 should be no big deal.',
'This \\0 should be no big deal.',
),
array(
'This \\0 should be no big deal.
',
'This \\0 should be no big deal.
',
),
array(
'This should be no big deal.
',
'This should be no big deal.
',
),
array(
'This is more of a concern.',
'This
is more of a concern.',
),
array(
'This
is more of a concern.',
'This
is more of a concern.',
),
array(
'This
is more of a concern.',
'This
is more of a concern.',
),
array(
'This
is more of a concern.',
'This
is more of a concern.',
),
array(
'This
is more of a concern.',
'This
is more of a concern.',
),
array(
'',
'div {background-image:\\0}',
),
);
}
/**
* Test new function wp_kses_hair_parse().
*
* @dataProvider data_hair_parse
*/
function test_hair_parse( $input, $output ) {
return $this->assertSame( $output, wp_kses_hair_parse( $input ) );
}
function data_hair_parse() {
return array(
array(
'title="hello" href="#" id="my_id" ',
array( 'title="hello" ', 'href="#" ', 'id="my_id" ' ),
),
array(
'[shortcode attr="value"] href="http://www.google.com/"title="moo"disabled',
array( '[shortcode attr="value"] ', 'href="http://www.google.com/"', 'title="moo"', 'disabled' ),
),
array(
'',
array(),
),
array(
'a',
array( 'a' ),
),
array(
'title="hello"disabled href=# id=\'my_id\'',
array( 'title="hello"', 'disabled ', 'href=# ', "id='my_id'" ),
),
array(
' ', // Calling function is expected to strip leading whitespace.
false,
),
array(
'abcd=abcd"abcd"',
false,
),
array(
"array[1]='z'z'z'z",
false,
),
// Using a digit in attribute name should work.
array(
'href="https://example.com/[shortcode attr=\'value\']" data-op3-timer-seconds="0"',
array( 'href="https://example.com/[shortcode attr=\'value\']" ', 'data-op3-timer-seconds="0"' ),
),
// Using an underscore in attribute name should work.
array(
'href="https://example.com/[shortcode attr=\'value\']" data-op_timer-seconds="0"',
array( 'href="https://example.com/[shortcode attr=\'value\']" ', 'data-op_timer-seconds="0"' ),
),
// Using a period in attribute name should work.
array(
'href="https://example.com/[shortcode attr=\'value\']" data-op.timer-seconds="0"',
array( 'href="https://example.com/[shortcode attr=\'value\']" ', 'data-op.timer-seconds="0"' ),
),
// Using a digit at the beginning of attribute name should return false.
array(
'href="https://example.com/[shortcode attr=\'value\']" 3data-op-timer-seconds="0"',
false,
),
);
}
/**
* Test new function wp_kses_attr_parse().
*
* @dataProvider data_attr_parse
*/
function test_attr_parse( $input, $output ) {
return $this->assertSame( $output, wp_kses_attr_parse( $input ) );
}
function data_attr_parse() {
return array(
array(
'
',
array( '' ),
),
array(
'',
array( '' ),
),
array(
'',
false,
),
array(
'a',
false,
),
array(
'',
array( '' ),
),
array(
'',
false,
),
array(
'',
array( '' ),
),
array(
'',
array( '' ),
),
array(
'',
false,
),
array(
"",
false,
),
array(
'
',
array( '
' ),
),
);
}
/**
* Test new function wp_kses_one_attr().
*
* @dataProvider data_one_attr
*/
function test_one_attr( $element, $input, $output ) {
return $this->assertSame( $output, wp_kses_one_attr( $input, $element ) );
}
function data_one_attr() {
return array(
array(
'a',
' title="hello" ',
' title="hello" ',
),
array(
'a',
'title = "hello"',
'title="hello"',
),
array(
'a',
"title='hello'",
"title='hello'",
),
array(
'a',
'title=hello',
'title="hello"',
),
array(
'a',
'href="javascript:alert(1)"',
'href="alert(1)"',
),
array(
'a',
'style ="style "',
'style="style"',
),
array(
'a',
'style="style "',
'style="style"',
),
array(
'a',
'style ="style ="',
'',
),
array(
'img',
'src="mypic.jpg"',
'src="mypic.jpg"',
),
array(
'img',
'loading="lazy"',
'loading="lazy"',
),
array(
'img',
'onerror=alert(1)',
'',
),
array(
'img',
'title=>',
'title=">"',
),
array(
'img',
'title="&garbage";"',
'title="&garbage";"',
),
);
}
/**
* @ticket 34063
*/
function test_bdo() {
global $allowedposttags;
$input = 'This is a BDO tag. Weird, right?
';
$this->assertSame( $input, wp_kses( $input, $allowedposttags ) );
}
/**
* @ticket 35079
*/
function test_ol_reversed() {
global $allowedposttags;
$input = '- Item 1
- Item 2
- Item 3
';
$this->assertSame( $input, wp_kses( $input, $allowedposttags ) );
}
/**
* @ticket 40680
*/
function test_wp_kses_attr_no_attributes_allowed_with_empty_array() {
$element = 'foo';
$attribute = 'title="foo" class="bar"';
$this->assertSame( "<{$element}>", wp_kses_attr( $element, $attribute, array( 'foo' => array() ), array() ) );
}
/**
* @ticket 40680
*/
function test_wp_kses_attr_no_attributes_allowed_with_true() {
$element = 'foo';
$attribute = 'title="foo" class="bar"';
$this->assertSame( "<{$element}>", wp_kses_attr( $element, $attribute, array( 'foo' => true ), array() ) );
}
/**
* @ticket 40680
*/
function test_wp_kses_attr_single_attribute_is_allowed() {
$element = 'foo';
$attribute = 'title="foo" class="bar"';
$this->assertSame( "<{$element} title=\"foo\">", wp_kses_attr( $element, $attribute, array( 'foo' => array( 'title' => true ) ), array() ) );
}
/**
* @ticket 43312
*/
function test_wp_kses_attr_no_attributes_allowed_with_false() {
$element = 'foo';
$attribute = 'title="foo" class="bar"';
$this->assertSame( "<{$element}>", wp_kses_attr( $element, $attribute, array( 'foo' => false ), array() ) );
}
/**
* Testing the safecss_filter_attr() function.
*
* @ticket 37248
* @ticket 42729
* @ticket 48376
* @dataProvider data_test_safecss_filter_attr
*
* @param string $css A string of CSS rules.
* @param string $expected Expected string of CSS rules.
*/
public function test_safecss_filter_attr( $css, $expected ) {
$this->assertSame( $expected, safecss_filter_attr( $css ) );
}
/**
* Data Provider for test_safecss_filter_attr().
*
* @return array {
* @type array {
* @string string $css A string of CSS rules.
* @string string $expected Expected string of CSS rules.
* }
* }
*/
public function data_test_safecss_filter_attr() {
return array(
// Empty input, empty output.
array(
'css' => '',
'expected' => '',
),
// An arbitrary attribute name isn't allowed.
array(
'css' => 'foo:bar',
'expected' => '',
),
// A single attribute name, with a single value.
array(
'css' => 'margin-top: 2px',
'expected' => 'margin-top: 2px',
),
// Backslash \ isn't supported.
array(
'css' => 'margin-top: \2px',
'expected' => '',
),
// Curly bracket } isn't supported.
array(
'css' => 'margin-bottom: 2px}',
'expected' => '',
),
// A single attribute name, with a single text value.
array(
'css' => 'text-transform: uppercase',
'expected' => 'text-transform: uppercase',
),
// Only lowercase attribute names are supported.
array(
'css' => 'Text-transform: capitalize',
'expected' => '',
),
// Uppercase attribute values goes through.
array(
'css' => 'text-transform: None',
'expected' => 'text-transform: None',
),
// A single attribute, with multiple values.
array(
'css' => 'font: bold 15px arial, sans-serif',
'expected' => 'font: bold 15px arial, sans-serif',
),
// Multiple attributes, with single values.
array(
'css' => 'font-weight: bold;font-size: 15px',
'expected' => 'font-weight: bold;font-size: 15px',
),
// Multiple attributes, separated by a space.
array(
'css' => 'font-weight: bold; font-size: 15px',
'expected' => 'font-weight: bold;font-size: 15px',
),
// Multiple attributes, with multiple values.
array(
'css' => 'margin: 10px 20px;padding: 5px 10px',
'expected' => 'margin: 10px 20px;padding: 5px 10px',
),
// Parenthesis ( is supported for some attributes.
array(
'css' => 'background: green url("foo.jpg") no-repeat fixed center',
'expected' => 'background: green url("foo.jpg") no-repeat fixed center',
),
// Additional background attributes introduced in 5.3.
array(
'css' => 'background-size: cover;background-size: 200px 100px;background-attachment: local, scroll;background-blend-mode: hard-light',
'expected' => 'background-size: cover;background-size: 200px 100px;background-attachment: local, scroll;background-blend-mode: hard-light',
),
// `border-radius` attribute introduced in 5.3.
array(
'css' => 'border-radius: 10% 30% 50% 70%;border-radius: 30px',
'expected' => 'border-radius: 10% 30% 50% 70%;border-radius: 30px',
),
// `flex` and related attributes introduced in 5.3.
array(
'css' => 'flex: 0 1 auto;flex-basis: 75%;flex-direction: row-reverse;flex-flow: row-reverse nowrap;flex-grow: 2;flex-shrink: 1',
'expected' => 'flex: 0 1 auto;flex-basis: 75%;flex-direction: row-reverse;flex-flow: row-reverse nowrap;flex-grow: 2;flex-shrink: 1',
),
// `grid` and related attributes introduced in 5.3.
array(
'css' => 'grid-template-columns: 1fr 60px;grid-auto-columns: min-content;grid-column-start: span 2;grid-column-end: -1;grid-column-gap: 10%;grid-gap: 10px 20px',
'expected' => 'grid-template-columns: 1fr 60px;grid-auto-columns: min-content;grid-column-start: span 2;grid-column-end: -1;grid-column-gap: 10%;grid-gap: 10px 20px',
),
array(
'css' => 'grid-template-rows: 40px 4em 40px;grid-auto-rows: min-content;grid-row-start: -1;grid-row-end: 3;grid-row-gap: 1em',
'expected' => 'grid-template-rows: 40px 4em 40px;grid-auto-rows: min-content;grid-row-start: -1;grid-row-end: 3;grid-row-gap: 1em',
),
// `grid` does not yet support functions or `\`.
array(
'css' => 'grid-template-columns: repeat(2, 50px 1fr);grid-template: 1em / 20% 20px 1fr',
'expected' => '',
),
// `flex` and `grid` alignments introduced in 5.3.
array(
'css' => 'align-content: space-between;align-items: start;align-self: center;justify-items: center;justify-content: space-between;justify-self: end',
'expected' => 'align-content: space-between;align-items: start;align-self: center;justify-items: center;justify-content: space-between;justify-self: end',
),
// `columns` and related attributes introduced in 5.3.
array(
'css' => 'columns: 6rem auto;column-count: 4;column-fill: balance;column-gap: 9px;column-rule: thick inset blue;column-span: none;column-width: 120px',
'expected' => 'columns: 6rem auto;column-count: 4;column-fill: balance;column-gap: 9px;column-rule: thick inset blue;column-span: none;column-width: 120px',
),
// Gradients introduced in 5.3.
array(
'css' => 'background: linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)',
'expected' => 'background: linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)',
),
array(
'css' => 'background: linear-gradient(135deg,rgba(6,147,227,1) ) (0%,rgb(155,81,224) 100%)',
'expected' => '',
),
array(
'css' => 'background-image: linear-gradient(red,yellow);',
'expected' => 'background-image: linear-gradient(red,yellow)',
),
array(
'css' => 'color: linear-gradient(red,yellow);',
'expected' => '',
),
array(
'css' => 'background-image: linear-gradient(red,yellow); background: prop( red,yellow); width: 100px;',
'expected' => 'background-image: linear-gradient(red,yellow);width: 100px',
),
array(
'css' => 'background: unknown-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)',
'expected' => '',
),
array(
'css' => 'background: repeating-linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)',
'expected' => 'background: repeating-linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)',
),
array(
'css' => 'width: 100px; height: 100px; background: linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%);',
'expected' => 'width: 100px;height: 100px;background: linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%)',
),
array(
'css' => 'background: radial-gradient(#ff0, red, yellow, green, rgba(6,147,227,1), rgb(155,81,224) 90%);',
'expected' => 'background: radial-gradient(#ff0, red, yellow, green, rgba(6,147,227,1), rgb(155,81,224) 90%)',
),
array(
'css' => 'background: radial-gradient(#ff0, red, yellow, green, rgba(6,147,227,1), rgb(155,81,224) 90%);',
'expected' => 'background: radial-gradient(#ff0, red, yellow, green, rgba(6,147,227,1), rgb(155,81,224) 90%)',
),
array(
'css' => 'background: conic-gradient(at 0% 30%, red 10%, yellow 30%, #1e90ff 50%)',
'expected' => 'background: conic-gradient(at 0% 30%, red 10%, yellow 30%, #1e90ff 50%)',
),
// Expressions are not allowed.
array(
'css' => 'height: expression( body.scrollTop + 50 + "px" )',
'expected' => '',
),
// RGB color values are not allowed.
array(
'css' => 'color: rgb( 100, 100, 100 )',
'expected' => '',
),
// RGBA color values are not allowed.
array(
'css' => 'color: rgb( 100, 100, 100, .4 )',
'expected' => '',
),
);
}
/**
* 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->assertSame( $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 ),
);
}
/**
* 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 );',
'',
),
);
}
/**
* Testing the safecss_filter_attr() function with the safecss_filter_attr_allow_css filter.
*
* @ticket 37134
*
* @dataProvider data_test_safecss_filter_attr_filtered
*
* @param string $css A string of CSS rules.
* @param string $expected Expected string of CSS rules.
*/
public function test_safecss_filter_attr_filtered( $css, $expected ) {
add_filter( 'safecss_filter_attr_allow_css', '__return_true' );
$this->assertSame( $expected, safecss_filter_attr( $css ) );
remove_filter( 'safecss_filter_attr_allow_css', '__return_true' );
}
/**
* Data Provider for test_safecss_filter_attr_filtered().
*
* @return array {
* @type array {
* @string string $css A string of CSS rules.
* @string string $expected Expected string of CSS rules.
* }
* }
*/
public function data_test_safecss_filter_attr_filtered() {
return array(
// A single attribute name, with a single value.
array(
'css' => 'margin-top: 2px',
'expected' => 'margin-top: 2px',
),
// Backslash \ can be allowed with the 'safecss_filter_attr_allow_css' filter.
array(
'css' => 'margin-top: \2px',
'expected' => 'margin-top: \2px',
),
// Curly bracket } can be allowed with the 'safecss_filter_attr_allow_css' filter.
array(
'css' => 'margin-bottom: 2px}',
'expected' => 'margin-bottom: 2px}',
),
// Parenthesis ) can be allowed with the 'safecss_filter_attr_allow_css' filter.
array(
'css' => 'margin-bottom: 2px)',
'expected' => 'margin-bottom: 2px)',
),
// Ampersand & can be allowed with the 'safecss_filter_attr_allow_css' filter.
array(
'css' => 'margin-bottom: 2px&',
'expected' => 'margin-bottom: 2px&',
),
// Expressions can be allowed with the 'safecss_filter_attr_allow_css' filter.
array(
'css' => 'height: expression( body.scrollTop + 50 + "px" )',
'expected' => 'height: expression( body.scrollTop + 50 + "px" )',
),
// RGB color values can be allowed with the 'safecss_filter_attr_allow_css' filter.
array(
'css' => 'color: rgb( 100, 100, 100 )',
'expected' => 'color: rgb( 100, 100, 100 )',
),
// RGBA color values can be allowed with the 'safecss_filter_attr_allow_css' filter.
array(
'css' => 'color: rgb( 100, 100, 100, .4 )',
'expected' => 'color: rgb( 100, 100, 100, .4 )',
),
);
}
/**
* Test filtering a standard img tag.
*
* @ticket 50731
*/
function test_wp_kses_img_tag_standard_attributes() {
$html = array(
'
',
);
$html = implode( ' ', $html );
$this->assertSame( $html, wp_kses_post( $html ) );
}
}