Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2bf9709
attempt to bring 29807.8.diff up to date with trunk
adamsilverstein Feb 26, 2024
11336a3
Merge remote-tracking branch 'upstream/trunk' into pr/6184
azaozz Mar 11, 2024
05dcd69
Fix coding standards spaces and methods visibility
azaozz Mar 11, 2024
499dc82
More CS empty space fixes.
azaozz Mar 11, 2024
7a75eba
Merge branch 'trunk' into ticket/29807
adamsilverstein Aug 15, 2025
69312e7
Apply suggestion from @azaozz
adamsilverstein Aug 15, 2025
3c6defd
string in_array test
adamsilverstein Aug 15, 2025
c88673d
expand test slightly
adamsilverstein Aug 15, 2025
022def5
Cleanup; move multi_uri to parameter
adamsilverstein Aug 15, 2025
321e441
phpcbf
adamsilverstein Aug 15, 2025
c7c63dd
add sizes attribute to test
adamsilverstein Aug 15, 2025
e3c1684
enable sizes
adamsilverstein Aug 15, 2025
3862627
Improve doc block
adamsilverstein Aug 15, 2025
4cb049a
test clean up
adamsilverstein Aug 15, 2025
ca94908
Update tests/phpunit/tests/kses.php
adamsilverstein Aug 17, 2025
49f1ba6
Update src/wp-includes/kses.php
adamsilverstein Aug 17, 2025
2b5706c
remove unused $uris
adamsilverstein Aug 17, 2025
d116c8e
Add additional test cases.
adamsilverstein Aug 17, 2025
fbeed82
Merge branch 'trunk' into ticket/29807
adamsilverstein Aug 27, 2025
f9e002c
phpcbf
adamsilverstein Aug 27, 2025
80521e4
Merge remote-tracking branch 'origin/trunk' into ticket/29807
adamsilverstein Mar 13, 2026
0068aac
Add tests exposing srcset and img attribute bugs
adamsilverstein Mar 13, 2026
f11aeb2
Add decoding and fetchpriority to img allowed attrs
adamsilverstein Mar 13, 2026
cdb84fc
Fix srcset URI sanitization for URLs with commas
adamsilverstein Mar 13, 2026
399aa30
Fix array double arrow alignment in kses tests
adamsilverstein Mar 13, 2026
e5488a6
Merge branch 'trunk' into ticket/29807
adamsilverstein Mar 14, 2026
1f02276
Add kses srcset/picture tests, update @since tag
adamsilverstein Mar 14, 2026
9bb1ebf
Fix array double arrow alignment in new tests
adamsilverstein Mar 14, 2026
2bdc1f9
Merge branch 'trunk' into ticket/29807
adamsilverstein Mar 14, 2026
61881bf
Merge branch 'trunk' into ticket/29807
adamsilverstein Apr 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 54 additions & 15 deletions src/wp-includes/kses.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,10 @@
'longdesc' => true,
'vspace' => true,
'src' => true,
'srcset' => true,
'usemap' => true,
'width' => true,
'sizes' => true,
),
'ins' => array(
'datetime' => true,
Expand Down Expand Up @@ -250,6 +252,7 @@
'p' => array(
'align' => true,
),
'picture' => array(),
'pre' => array(
'width' => true,
),
Expand All @@ -270,6 +273,12 @@
'align' => true,
),
'small' => array(),
'source' => array(
'srcset' => true,
'type' => true,
'media' => true,
'sizes' => true,
),
'strike' => array(),
'strong' => array(),
'sub' => array(),
Expand Down Expand Up @@ -768,7 +777,6 @@ function wp_kses( $content, $allowed_html, $allowed_protocols = array() ) {
* @return string Filtered attribute.
*/
function wp_kses_one_attr( $attr, $element ) {
Comment thread
adamsilverstein marked this conversation as resolved.
$uris = wp_kses_uri_attributes();
$allowed_html = wp_kses_allowed_html( 'post' );
$allowed_protocols = wp_allowed_protocols();
$attr = wp_kses_no_null( $attr, array( 'slash_zero' => 'keep' ) );
Expand Down Expand Up @@ -812,10 +820,7 @@ function wp_kses_one_attr( $attr, $element ) {
// Sanitize quotes, angle braces, and entities.
$value = esc_attr( $value );

// Sanitize URI values.
if ( in_array( strtolower( $name ), $uris, true ) ) {
$value = wp_kses_bad_protocol( $value, $allowed_protocols );
}
$value = wp_kses_sanitize_uris( $name, $value, $allowed_protocols );

$attr = "$name=$quote$value$quote";
$vless = 'n';
Expand Down Expand Up @@ -1034,6 +1039,7 @@ function wp_kses_uri_attributes() {
'src',
'usemap',
'xmlns',
'srcset',
);

/**
Expand Down Expand Up @@ -1394,7 +1400,6 @@ function wp_kses_hair( $attr, $allowed_protocols ) {
$attrarr = array();
$mode = 0;
$attrname = '';
$uris = wp_kses_uri_attributes();

// Loop through the whole attribute list.

Expand Down Expand Up @@ -1442,9 +1447,9 @@ function wp_kses_hair( $attr, $allowed_protocols ) {
if ( preg_match( '%^"([^"]*)"(\s+|/?$)%', $attr, $match ) ) {
// "value"
$thisval = $match[1];
if ( in_array( strtolower( $attrname ), $uris, true ) ) {
$thisval = wp_kses_bad_protocol( $thisval, $allowed_protocols );
}

// Sanitize URI values.
$thisval = wp_kses_sanitize_uris( $attrname, $thisval, $allowed_protocols );

if ( false === array_key_exists( $attrname, $attrarr ) ) {
$attrarr[ $attrname ] = array(
Expand All @@ -1464,9 +1469,8 @@ function wp_kses_hair( $attr, $allowed_protocols ) {
if ( preg_match( "%^'([^']*)'(\s+|/?$)%", $attr, $match ) ) {
// 'value'
$thisval = $match[1];
if ( in_array( strtolower( $attrname ), $uris, true ) ) {
$thisval = wp_kses_bad_protocol( $thisval, $allowed_protocols );
}
// Sanitize URI values.
$thisval = wp_kses_sanitize_uris( $attrname, $thisval, $allowed_protocols );

if ( false === array_key_exists( $attrname, $attrarr ) ) {
$attrarr[ $attrname ] = array(
Expand All @@ -1486,9 +1490,8 @@ function wp_kses_hair( $attr, $allowed_protocols ) {
if ( preg_match( "%^([^\s\"']+)(\s+|/?$)%", $attr, $match ) ) {
// value
$thisval = $match[1];
if ( in_array( strtolower( $attrname ), $uris, true ) ) {
$thisval = wp_kses_bad_protocol( $thisval, $allowed_protocols );
}
// Sanitize URI values.
$thisval = wp_kses_sanitize_uris( $attrname, $thisval, $allowed_protocols );

if ( false === array_key_exists( $attrname, $attrarr ) ) {
$attrarr[ $attrname ] = array(
Expand Down Expand Up @@ -1530,6 +1533,42 @@ function wp_kses_hair( $attr, $allowed_protocols ) {
return $attrarr;
}

/**
* Sanitizes URI values in HTML attributes.
*
* This function centralizes logic for cleaning attribute values that are expected to contain URLs.
* It checks if the attribute name is one that should contain a URI (e.g., 'href', 'src', 'srcset').
* For attributes that can contain multiple URIs (such as 'srcset'), it splits the value and sanitizes each URI individually.
* All URI values are passed through {@see wp_kses_bad_protocol()} to remove disallowed protocols (e.g., 'javascript:').
*
* @since 6.9.0
*
* @param string $attrname The attribute name to test.
* @param string $attrvalue The attribute value to sanitize.
* @param string[] $allowed_protocols Array of allowed URL protocols.
* @param string[] $multi_uri Optional. Attributes that can contain multiple URIs. Default is array( 'srcset' ).
* @return string Sanitized attribute value.
*/
function wp_kses_sanitize_uris( $attrname, $attrvalue, $allowed_protocols, $multi_uri = array( 'srcset' ) ) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these can add PHP type hints as well for the params and the return value.

$uris = wp_kses_uri_attributes();

if ( ! in_array( strtolower( $attrname ), $uris, true ) ) {
return $attrvalue;
} else {
if ( in_array( strtolower( $attrname ), $multi_uri, true ) ) {
$thesevals = preg_split( '/\s*,\s*/', $attrvalue );
} else {
$thesevals = array( $attrvalue );
}
Comment thread
adamsilverstein marked this conversation as resolved.
Outdated
Comment thread
adamsilverstein marked this conversation as resolved.
Outdated
}

foreach ( (array) $thesevals as $key => $val ) {
$thesevals[ $key ] = wp_kses_bad_protocol( $val, $allowed_protocols );
}

return implode( ', ', $thesevals );
}

/**
* Finds all attributes of an HTML element.
*
Expand Down
96 changes: 96 additions & 0 deletions tests/phpunit/tests/kses.php
Original file line number Diff line number Diff line change
Expand Up @@ -2377,4 +2377,100 @@ public function data_allowed_attributes_in_descriptions() {
),
);
}

/**
* Test that wp_filter_post_kses() filters img tags correctly and allows the srcset element.
*
* @ticket 29807
*/
public function test_wp_filter_post_kses_img() {
global $allowedposttags;

$attributes = array(
'class' => 'classname',
'id' => 'idattr',
'style' => 'color: red;',
'alt' => 'alt',
'src' => '/test.png',
'srcset' => '/test.png 1x, /test-2x.png 2x, /test-3x.png',
'width' => '100',
'height' => '100',
'usemap' => '#hash',
'vspace' => '20',
'hspace' => '20',
'longdesc' => 'this is the longdesc',
'align' => 'middle',
'border' => '5',
'sizes' => '(max-width: 600px) 100vw, 50vw',
);

foreach ( $attributes as $name => $value ) {
if ( $name === $value ) {
$string = "<img $value />";
$expect_string = '<img ' . trim( $value, ';' ) . ' />';
} else {
$string = "<img $name='$value' />";
$expect_string = "<img $name='" . trim( $value, ';' ) . "' />";
}

$this->assertEquals( $expect_string, wp_kses( $string, $allowedposttags ) );
}
}

/**
* @ticket 29807
*
* @param string $unfiltered Unfiltered srcset value before wp_kses.
* @param string $expected Expected srcset value after wp_kses.
*
* @dataProvider data_wp_kses_srcset
*/
public function test_wp_kses_srcset( $unfiltered, $expected ) {
$unfiltered = "<img src='test.png' srcset='{$unfiltered}' />";
$expected = "<img src='test.png' srcset='{$expected}' />";
$this->assertEquals( $expected, wp_kses_post( $unfiltered ) );
}

public function data_wp_kses_srcset() {
return array(
array(
'/test.png 1x, /test-2x.png 2x',
'/test.png 1x, /test-2x.png 2x',
),
array(
'bad://localhost/test.png 1x, http://localhost/test-2x.png 2x',
'//localhost/test.png 1x, http://localhost/test-2x.png 2x',
),
array(
'http://localhost/test.png 1x, bad://localhost/test-2x.png 2x',
'http://localhost/test.png 1x, //localhost/test-2x.png 2x',
),
array(
'http://localhost/test.png,big 1x, bad://localhost/test.png,medium 2x',
'http://localhost/test.png, big 1x, //localhost/test.png, medium 2x',
),
array(
'path/to/test.png 1x, path/to/test-2x.png 2x',
'path/to/test.png 1x, path/to/test-2x.png 2x',
),
);
}

/**
* @ticket 29807
*/
public function test_wp_filter_post_kses_picture() {
global $allowedposttags;

$html = '<picture><source srcset="pear-mobile.jpeg" media="(max-width: 720px)" type="image/png"><source srcset="pear-tablet.jpeg" media="(max-width: 1280px)" type="image/png"><img src="pear-desktop.jpeg" alt="The pear is juicy."></picture>';
$this->assertEquals( $html, wp_kses( $html, $allowedposttags ) );

$html = '<picture><source srcset="https://wordpress.org/pear-mobile.jpeg" media="(max-width: 720px)" type="image/png"><source srcset="https://wordpress.org/pear-tablet.jpeg 500w, https://wordpress.org/pear-tablet.jpeg" media="(max-width: 1280px)" type="image/png"><img src="pear-desktop.jpeg" alt="The pear is juicy."></picture>';
$this->assertEquals( $html, wp_kses( $html, $allowedposttags ) );

// Test bad protocol in srcset
$original = '<picture><source srcset="bad://pear-mobile.jpeg" media="(max-width: 720px)" type="image/png"><source srcset="pear-tablet.jpeg" media="(max-width: 1280px)" type="image/png"><img src="pear-desktop.jpeg" alt="The pear is juicy."></picture>';
$expected = '<picture><source srcset="//pear-mobile.jpeg" media="(max-width: 720px)" type="image/png"><source srcset="pear-tablet.jpeg" media="(max-width: 1280px)" type="image/png"><img src="pear-desktop.jpeg" alt="The pear is juicy."></picture>';
$this->assertEquals( $expected, wp_kses( $original, $allowedposttags ) );
}
}
Loading