From 2bf9709d5029c76df20c6ab60766714a67003879 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Mon, 26 Feb 2024 11:42:15 -0700 Subject: [PATCH 01/17] attempt to bring 29807.8.diff up to date with trunk --- src/wp-includes/kses.php | 65 +++++++++++++++++++------ tests/phpunit/tests/kses.php | 94 ++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 14 deletions(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index cccb1768c2dfd..112ce6ac27165 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -206,6 +206,7 @@ 'longdesc' => true, 'vspace' => true, 'src' => true, + 'srcset' => true, 'usemap' => true, 'width' => true, ), @@ -250,6 +251,7 @@ 'p' => array( 'align' => true, ), + 'picture' => array(), 'pre' => array( 'width' => true, ), @@ -270,6 +272,12 @@ 'align' => true, ), 'small' => array(), + 'source' => array( + 'srcset' => true, + 'type' => true, + 'media' => true, + 'sizes' => true, + ), 'strike' => array(), 'strong' => array(), 'sub' => array(), @@ -768,7 +776,6 @@ function wp_kses( $content, $allowed_html, $allowed_protocols = array() ) { * @return string Filtered attribute. */ function wp_kses_one_attr( $attr, $element ) { - $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' ) ); @@ -812,10 +819,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'; @@ -1380,9 +1384,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( @@ -1402,9 +1406,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( @@ -1424,9 +1427,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( @@ -1468,6 +1470,41 @@ function wp_kses_hair( $attr, $allowed_protocols ) { return $attrarr; } +/** + * Santizes uris in attributes + * + * This condenses code that was spread around two functions and several cases. It places the list of attributes + * which can have uris in them and checks for ones that can have multiple uri candidates (like srcset) + * It ultimately passes everything to wp_kses_bad_protocol() to do the actual work. + * + * @since ? + * + * @param string $attrname Attribute name to test against. + * @param string $attrvalue Content to filter bad protocols from. + * @param string[] $allowed_protocols Array of allowed URL protocols. + * @return string Filtered content. + */ +function wp_kses_sanitize_uris( $attrname, $attrvalue, $allowed_protocols ) { + $uris = wp_kses_uri_attributes(); + $uri_candidates = array( 'srcset' ); + + if ( ! in_array( strtolower( $attrname ), $uris ) ) { + return $attrvalue; + } else { + if ( in_array( strtolower( $attrname ), $uri_candidates ) ) { + $thesevals = preg_split( '/\s*,\s+/', $attrvalue ); + } else { + $thesevals = array( $attrvalue ); + } + } + + foreach ( (array) $thesevals as $key => $val ) { + $thesevals[$key] = wp_kses_bad_protocol( $val, $allowed_protocols ); + } + + return join(', ', $thesevals); +} + /** * Finds all attributes of an HTML element. * diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index 1dbe5b2587bc4..3a569c6e5f760 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -2188,4 +2188,98 @@ public function data_kses_globals_are_defined() { return $this->text_array_to_dataprovider( $required_kses_globals ); } + + /** + * @ticket 29807 + */ + 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', + 'width' => '100', + 'height' => '100', + 'usemap' => '#hash', + 'vspace' => '20', + 'hspace' => '20', + 'longdesc' => 'this is the longdesc', + 'align' => 'middle', + 'border' => '5', + ); + + foreach ( $attributes as $name => $value ) { + if ( $name === $value ) { + $string = ""; + $expect_string = ''; + } else { + $string = ""; + $expect_string = ""; + } + + $this->assertEquals( $expect_string, wp_kses( $string, $allowedposttags ) ); + } + } + + /** + * @ticket 29807 + * + * @param string $unfiltered Unfiltered srcset value before wp_kses. + * @param string $expected Expected srset value after wp_kses. + * + * @dataProvider data_wp_kses_srcset + */ + function test_wp_kses_srcset( $unfiltered, $expected ) { + $unfiltered = ""; + $expected = ""; + $this->assertEquals( $expected, wp_kses_post( $unfiltered ) ); + } + + 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 + */ + function test_wp_filter_post_kses_picture() { + global $allowedposttags; + + $html = 'The pear is juicy.'; + $this->assertEquals( $html, wp_kses( $html, $allowedposttags ) ); + + $html = 'The pear is juicy.'; + $this->assertEquals( $html, wp_kses( $html, $allowedposttags ) ); + + // Test bad protocol in srcset + $original = 'The pear is juicy.'; + $expected = 'The pear is juicy.'; + $this->assertEquals( $expected, wp_kses( $original, $allowedposttags ) ); + } + } From 05dcd69c1f06ee81330d11c836617e9badf2ef4b Mon Sep 17 00:00:00 2001 From: Andrew Ozz <743931+azaozz@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:20:27 -0700 Subject: [PATCH 02/17] Fix coding standards spaces and methods visibility --- src/wp-includes/kses.php | 18 +++++++++--------- tests/phpunit/tests/kses.php | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 112ce6ac27165..ec85cb43d78c7 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -206,7 +206,7 @@ 'longdesc' => true, 'vspace' => true, 'src' => true, - 'srcset' => true, + 'srcset' => true, 'usemap' => true, 'width' => true, ), @@ -251,7 +251,7 @@ 'p' => array( 'align' => true, ), - 'picture' => array(), + 'picture' => array(), 'pre' => array( 'width' => true, ), @@ -272,12 +272,12 @@ 'align' => true, ), 'small' => array(), - 'source' => array( - 'srcset' => true, - 'type' => true, - 'media' => true, - 'sizes' => true, - ), + 'source' => array( + 'srcset' => true, + 'type' => true, + 'media' => true, + 'sizes' => true, + ), 'strike' => array(), 'strong' => array(), 'sub' => array(), @@ -1494,7 +1494,7 @@ function wp_kses_sanitize_uris( $attrname, $attrvalue, $allowed_protocols ) { if ( in_array( strtolower( $attrname ), $uri_candidates ) ) { $thesevals = preg_split( '/\s*,\s+/', $attrvalue ); } else { - $thesevals = array( $attrvalue ); + $thesevals = array( $attrvalue ); } } diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index 3a569c6e5f760..522ad0cc7205c 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -2192,8 +2192,8 @@ public function data_kses_globals_are_defined() { /** * @ticket 29807 */ - function test_wp_filter_post_kses_img() { - global $allowedposttags; + public function test_wp_filter_post_kses_img() { + global $allowedposttags; $attributes = array( 'class' => 'classname', @@ -2233,13 +2233,13 @@ function test_wp_filter_post_kses_img() { * * @dataProvider data_wp_kses_srcset */ - function test_wp_kses_srcset( $unfiltered, $expected ) { + public function test_wp_kses_srcset( $unfiltered, $expected ) { $unfiltered = ""; $expected = ""; $this->assertEquals( $expected, wp_kses_post( $unfiltered ) ); } - function data_wp_kses_srcset() { + public function data_wp_kses_srcset() { return array( array( '/test.png 1x, /test-2x.png 2x', @@ -2267,7 +2267,7 @@ function data_wp_kses_srcset() { /** * @ticket 29807 */ - function test_wp_filter_post_kses_picture() { + public function test_wp_filter_post_kses_picture() { global $allowedposttags; $html = 'The pear is juicy.'; From 499dc82da9fa4a081f2e07c306b1bd8f90c6f90d Mon Sep 17 00:00:00 2001 From: Andrew Ozz <743931+azaozz@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:26:37 -0700 Subject: [PATCH 03/17] More CS empty space fixes. --- src/wp-includes/kses.php | 4 ++-- tests/phpunit/tests/kses.php | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index ec85cb43d78c7..cb12e4dd958c5 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -1499,10 +1499,10 @@ function wp_kses_sanitize_uris( $attrname, $attrvalue, $allowed_protocols ) { } foreach ( (array) $thesevals as $key => $val ) { - $thesevals[$key] = wp_kses_bad_protocol( $val, $allowed_protocols ); + $thesevals[ $key ] = wp_kses_bad_protocol( $val, $allowed_protocols ); } - return join(', ', $thesevals); + return join( ', ', $thesevals ); } /** diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index 522ad0cc7205c..0bd240eb1d91a 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -2281,5 +2281,4 @@ public function test_wp_filter_post_kses_picture() { $expected = 'The pear is juicy.'; $this->assertEquals( $expected, wp_kses( $original, $allowedposttags ) ); } - } From 69312e797cba46b37411342bc7da127bcee6644b Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 15 Aug 2025 13:18:07 -0600 Subject: [PATCH 04/17] Apply suggestion from @azaozz Co-authored-by: Andrew Ozz <743931+azaozz@users.noreply.github.com> --- src/wp-includes/kses.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 4a06a6c34be72..ccf1ae901d6b4 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -1554,7 +1554,7 @@ function wp_kses_sanitize_uris( $attrname, $attrvalue, $allowed_protocols ) { return $attrvalue; } else { if ( in_array( strtolower( $attrname ), $uri_candidates ) ) { - $thesevals = preg_split( '/\s*,\s+/', $attrvalue ); + $thesevals = preg_split( '/\s*,\s*/', $attrvalue ); } else { $thesevals = array( $attrvalue ); } From 3c6defd54d513213e0aaf587e701831dfe34a60e Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Fri, 15 Aug 2025 13:29:44 -0600 Subject: [PATCH 05/17] string in_array test --- src/wp-includes/kses.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index ccf1ae901d6b4..5123f8b2bab9c 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -1553,7 +1553,7 @@ function wp_kses_sanitize_uris( $attrname, $attrvalue, $allowed_protocols ) { if ( ! in_array( strtolower( $attrname ), $uris ) ) { return $attrvalue; } else { - if ( in_array( strtolower( $attrname ), $uri_candidates ) ) { + if ( in_array( strtolower( $attrname ), $uri_candidates, true ) ) { $thesevals = preg_split( '/\s*,\s*/', $attrvalue ); } else { $thesevals = array( $attrvalue ); From c88673d893b63a72e901bfe89d71bca79d1ec194 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Fri, 15 Aug 2025 13:29:53 -0600 Subject: [PATCH 06/17] expand test slightly --- tests/phpunit/tests/kses.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index efc4936f82cbd..e1ceeab5d368f 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -2390,7 +2390,7 @@ public function test_wp_filter_post_kses_img() { 'style' => 'color: red;', 'alt' => 'alt', 'src' => '/test.png', - 'srcset' => '/test.png 1x, /test-2x.png 2x', + 'srcset' => '/test.png 1x, /test-2x.png 2x,/test-3x.png', 'width' => '100', 'height' => '100', 'usemap' => '#hash', From 022def51675a252759551ee7542a699b80f8cf0e Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Fri, 15 Aug 2025 13:38:22 -0600 Subject: [PATCH 07/17] Cleanup; move multi_uri to parameter --- src/wp-includes/kses.php | 14 +++++++------- tests/phpunit/tests/kses.php | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 5123f8b2bab9c..df73ce7f81e1d 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -1539,21 +1539,21 @@ function wp_kses_hair( $attr, $allowed_protocols ) { * which can have uris in them and checks for ones that can have multiple uri candidates (like srcset) * It ultimately passes everything to wp_kses_bad_protocol() to do the actual work. * - * @since ? + * @since 6.9.0 * - * @param string $attrname Attribute name to test against. - * @param string $attrvalue Content to filter bad protocols from. + * @param string. $attrname Attribute name to test against. + * @param string $attrvalue Content to filter bad protocols from. * @param string[] $allowed_protocols Array of allowed URL protocols. + * @param string[] $multi_uri Array of attributes that can have multiple uri candidates. Defaults to ['srcset']. * @return string Filtered content. */ -function wp_kses_sanitize_uris( $attrname, $attrvalue, $allowed_protocols ) { - $uris = wp_kses_uri_attributes(); - $uri_candidates = array( 'srcset' ); +function wp_kses_sanitize_uris( $attrname, $attrvalue, $allowed_protocols, $multi_uri = array( 'srcset' ) ) { + $uris = wp_kses_uri_attributes(); if ( ! in_array( strtolower( $attrname ), $uris ) ) { return $attrvalue; } else { - if ( in_array( strtolower( $attrname ), $uri_candidates, true ) ) { + if ( in_array( strtolower( $attrname ), $multi_uri, true ) ) { $thesevals = preg_split( '/\s*,\s*/', $attrvalue ); } else { $thesevals = array( $attrvalue ); diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index e1ceeab5d368f..25f65b1d1c691 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -2379,6 +2379,8 @@ 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() { From 321e441e88e27b34fbd6b054e0fd822318bf92cd Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Fri, 15 Aug 2025 13:40:21 -0600 Subject: [PATCH 08/17] phpcbf --- tests/phpunit/tests/kses.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index 25f65b1d1c691..6cf4b3b4a42c7 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -2387,28 +2387,28 @@ 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', + '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', + 'align' => 'middle', + 'border' => '5', ); foreach ( $attributes as $name => $value ) { if ( $name === $value ) { - $string = ""; + $string = ""; $expect_string = ''; } else { - $string = ""; + $string = ""; $expect_string = ""; } @@ -2426,7 +2426,7 @@ public function test_wp_filter_post_kses_img() { */ public function test_wp_kses_srcset( $unfiltered, $expected ) { $unfiltered = ""; - $expected = ""; + $expected = ""; $this->assertEquals( $expected, wp_kses_post( $unfiltered ) ); } From c7c63ddf511982a4fd64e31aba4a620a023ef715 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Fri, 15 Aug 2025 13:45:35 -0600 Subject: [PATCH 09/17] add sizes attribute to test --- tests/phpunit/tests/kses.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index 6cf4b3b4a42c7..c5819a70bf961 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -2401,6 +2401,7 @@ public function test_wp_filter_post_kses_img() { 'longdesc' => 'this is the longdesc', 'align' => 'middle', 'border' => '5', + 'sizes' => '(width <= 600px) 480px, 800px', ); foreach ( $attributes as $name => $value ) { From e3c1684bca8830ebc2dbf14a5050dbe469e85f5a Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Fri, 15 Aug 2025 13:46:27 -0600 Subject: [PATCH 10/17] enable sizes --- src/wp-includes/kses.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index df73ce7f81e1d..ab1175bdec4bf 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -209,6 +209,7 @@ 'srcset' => true, 'usemap' => true, 'width' => true, + 'sizes' => true, ), 'ins' => array( 'datetime' => true, From 386262740f6a168ba0702fa5a9ab3de739251a9d Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Fri, 15 Aug 2025 14:51:19 -0600 Subject: [PATCH 11/17] Improve doc block --- src/wp-includes/kses.php | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index ab1175bdec4bf..b8c78c51bd82a 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -1039,6 +1039,7 @@ function wp_kses_uri_attributes() { 'src', 'usemap', 'xmlns', + 'srcset', ); /** @@ -1534,24 +1535,25 @@ function wp_kses_hair( $attr, $allowed_protocols ) { } /** - * Santizes uris in attributes + * Sanitizes URI values in HTML attributes. * - * This condenses code that was spread around two functions and several cases. It places the list of attributes - * which can have uris in them and checks for ones that can have multiple uri candidates (like srcset) - * It ultimately passes everything to wp_kses_bad_protocol() to do the actual work. + * 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 Attribute name to test against. - * @param string $attrvalue Content to filter bad protocols from. + * @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 Array of attributes that can have multiple uri candidates. Defaults to ['srcset']. - * @return string Filtered content. + * @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' ) ) { $uris = wp_kses_uri_attributes(); - if ( ! in_array( strtolower( $attrname ), $uris ) ) { + if ( ! in_array( strtolower( $attrname ), $uris, true ) ) { return $attrvalue; } else { if ( in_array( strtolower( $attrname ), $multi_uri, true ) ) { From 4cb049a51606dd6ac387eed1a95022270ffd8366 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Fri, 15 Aug 2025 15:47:17 -0600 Subject: [PATCH 12/17] test clean up --- tests/phpunit/tests/kses.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index c5819a70bf961..a7aca1611da9e 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -2392,7 +2392,7 @@ public function test_wp_filter_post_kses_img() { 'style' => 'color: red;', 'alt' => 'alt', 'src' => '/test.png', - 'srcset' => '/test.png 1x, /test-2x.png 2x,/test-3x.png', + 'srcset' => '/test.png 1x, /test-2x.png 2x, /test-3x.png', 'width' => '100', 'height' => '100', 'usemap' => '#hash', @@ -2401,7 +2401,7 @@ public function test_wp_filter_post_kses_img() { 'longdesc' => 'this is the longdesc', 'align' => 'middle', 'border' => '5', - 'sizes' => '(width <= 600px) 480px, 800px', + 'sizes' => '(max-width: 600px) 100vw, 50vw', ); foreach ( $attributes as $name => $value ) { @@ -2447,7 +2447,7 @@ public function data_wp_kses_srcset() { ), array( 'http://localhost/test.png,big 1x, bad://localhost/test.png,medium 2x', - 'http://localhost/test.png,big 1x, //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', From ca94908a52b1423e7bb23a46a937864da595482f Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 17 Aug 2025 10:35:14 -0600 Subject: [PATCH 13/17] Update tests/phpunit/tests/kses.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/phpunit/tests/kses.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index a7aca1611da9e..cc62e9a199233 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -2421,7 +2421,7 @@ public function test_wp_filter_post_kses_img() { * @ticket 29807 * * @param string $unfiltered Unfiltered srcset value before wp_kses. - * @param string $expected Expected srset value after wp_kses. + * @param string $expected Expected srcset value after wp_kses. * * @dataProvider data_wp_kses_srcset */ From 49f1ba61c7dd4e9ec5cd4f3b06016048016df0ed Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 17 Aug 2025 10:36:27 -0600 Subject: [PATCH 14/17] Update src/wp-includes/kses.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/wp-includes/kses.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index b8c78c51bd82a..7ad84dae2a349 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -1567,7 +1567,7 @@ function wp_kses_sanitize_uris( $attrname, $attrvalue, $allowed_protocols, $mult $thesevals[ $key ] = wp_kses_bad_protocol( $val, $allowed_protocols ); } - return join( ', ', $thesevals ); + return implode( ', ', $thesevals ); } /** From 2b5706c005989f90d7eb98d6ab76d0d633d2bbd4 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 17 Aug 2025 10:40:02 -0600 Subject: [PATCH 15/17] remove unused $uris --- src/wp-includes/kses.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 7ad84dae2a349..7baa14210d51f 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -1400,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. From d116c8e20abb1219dc3690dd2b95510546be5ab5 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 17 Aug 2025 10:56:17 -0600 Subject: [PATCH 16/17] Add additional test cases. --- tests/phpunit/tests/kses.php | 140 +++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index cc62e9a199233..cac7255aff48e 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -2473,4 +2473,144 @@ public function test_wp_filter_post_kses_picture() { $expected = 'The pear is juicy.'; $this->assertEquals( $expected, wp_kses( $original, $allowedposttags ) ); } + + /** + * Test wp_kses_sanitize_uris function directly. + * + * @ticket 29807 + * @dataProvider data_wp_kses_sanitize_uris + */ + public function test_wp_kses_sanitize_uris( $attrname, $attrvalue, $expected, $multi_uri = array( 'srcset' ) ) { + $allowed_protocols = wp_allowed_protocols(); + $result = wp_kses_sanitize_uris( $attrname, $attrvalue, $allowed_protocols, $multi_uri ); + $this->assertEquals( $expected, $result ); + } + + public function data_wp_kses_sanitize_uris() { + return array( + // Test non-URI attribute. + array( 'alt', 'description', 'description' ), + + // Test single URI attribute. + array( 'src', 'http://example.com/image.jpg', 'http://example.com/image.jpg' ), + + // Test single URI with bad protocol. + array( 'src', 'javascript:alert(1)', 'alert(1)' ), + + // Test srcset with multiple URIs. + array( 'srcset', 'image1.jpg 1x, image2.jpg 2x', 'image1.jpg 1x, image2.jpg 2x' ), + + // Test srcset with bad protocol. + array( 'srcset', 'javascript:alert(1) 1x, http://example.com/image.jpg 2x', 'alert(1) 1x, http://example.com/image.jpg 2x' ), + + // Test custom multi_uri parameter. + array( 'custom', 'url1.jpg, url2.jpg', 'url1.jpg, url2.jpg', array( 'custom' ) ), + ); + } + + /** + * Test edge cases for srcset sanitization. + * + * @ticket 29807 + * @dataProvider data_wp_kses_srcset_edge_cases + */ + public function test_wp_kses_srcset_edge_cases( $srcset_value, $expected ) { + $allowed_protocols = wp_allowed_protocols(); + $result = wp_kses_sanitize_uris( 'srcset', $srcset_value, $allowed_protocols ); + $this->assertEquals( $expected, $result ); + } + + public function data_wp_kses_srcset_edge_cases() { + return array( + // Test an empty srcset. + array( '', '' ), + + // Srcset with extra whitespace. + array( ' image1.jpg 1x , image2.jpg 2x ', ' image1.jpg 1x, image2.jpg 2x ' ), + + // Srcset with single URL and no descriptor. + array( 'image.jpg', 'image.jpg' ), + + // Srcset with complex descriptors. + array( 'small.jpg 480w, medium.jpg 800w, large.jpg 1200w', 'small.jpg 480w, medium.jpg 800w, large.jpg 1200w' ), + ); + } + + /** + * Test malicious input sanitization in srcset. + * + * @ticket 29807 + */ + public function test_wp_kses_malicious_input() { + global $allowedposttags; + + // JavaScript in srcset - the entire img tag gets escaped when it contains dangerous content. + $original = ''; + $result = wp_kses( $original, $allowedposttags ); + // The whole img tag should be escaped when it contains script content. + $this->assertStringStartsWith( '<', $result ); + + // Script tag in picture element (should be stripped). + $original = ''; + $result = wp_kses( $original, $allowedposttags ); + // Script content should be converted to text, not completely removed. + $this->assertStringContainsString( 'alert(1)', $result ); + $this->assertStringNotContainsString( ' 2x" />'; - $result = wp_kses( $original, $allowedposttags ); + $result = wp_kses( $original, $allowedposttags ); // The whole img tag should be escaped when it contains script content. $this->assertStringStartsWith( '<', $result ); // Script tag in picture element (should be stripped). $original = ''; - $result = wp_kses( $original, $allowedposttags ); + $result = wp_kses( $original, $allowedposttags ); // Script content should be converted to text, not completely removed. $this->assertStringContainsString( 'alert(1)', $result ); $this->assertStringNotContainsString( '