--- a/wp/wp-includes/kses.php Thu Sep 29 08:06:27 2022 +0200
+++ b/wp/wp-includes/kses.php Fri Sep 05 18:40:08 2025 +0200
@@ -36,6 +36,13 @@
* Using `CUSTOM_TAGS` is not recommended and should be considered deprecated. The
* {@see 'wp_kses_allowed_html'} filter is more powerful and supplies context.
*
+ * When using this constant, make sure to set all of these globals to arrays:
+ *
+ * - `$allowedposttags`
+ * - `$allowedtags`
+ * - `$allowedentitynames`
+ * - `$allowedxmlentitynames`
+ *
* @see wp_kses_allowed_html()
* @since 1.2.0
*
@@ -685,6 +692,33 @@
$allowedposttags = array_map( '_wp_add_global_attributes', $allowedposttags );
} else {
+ $required_kses_globals = array(
+ 'allowedposttags',
+ 'allowedtags',
+ 'allowedentitynames',
+ 'allowedxmlentitynames',
+ );
+ $missing_kses_globals = array();
+
+ foreach ( $required_kses_globals as $global_name ) {
+ if ( ! isset( $GLOBALS[ $global_name ] ) || ! is_array( $GLOBALS[ $global_name ] ) ) {
+ $missing_kses_globals[] = '<code>$' . $global_name . '</code>';
+ }
+ }
+
+ if ( $missing_kses_globals ) {
+ _doing_it_wrong(
+ 'wp_kses_allowed_html',
+ sprintf(
+ /* translators: 1: CUSTOM_TAGS, 2: Global variable names. */
+ __( 'When using the %1$s constant, make sure to set these globals to an array: %2$s.' ),
+ '<code>CUSTOM_TAGS</code>',
+ implode( ', ', $missing_kses_globals )
+ ),
+ '6.2.0'
+ );
+ }
+
$allowedtags = wp_kses_array_lc( $allowedtags );
$allowedposttags = wp_kses_array_lc( $allowedposttags );
}
@@ -702,23 +736,24 @@
*
* @since 1.0.0
*
- * @param string $string Text content to filter.
+ * @param string $content Text content to filter.
* @param array[]|string $allowed_html An array of allowed HTML elements and attributes,
* or a context name such as 'post'. See wp_kses_allowed_html()
* for the list of accepted context names.
- * @param string[] $allowed_protocols Array of allowed URL protocols.
+ * @param string[] $allowed_protocols Optional. Array of allowed URL protocols.
+ * Defaults to the result of wp_allowed_protocols().
* @return string Filtered content containing only the allowed HTML.
*/
-function wp_kses( $string, $allowed_html, $allowed_protocols = array() ) {
+function wp_kses( $content, $allowed_html, $allowed_protocols = array() ) {
if ( empty( $allowed_protocols ) ) {
$allowed_protocols = wp_allowed_protocols();
}
- $string = wp_kses_no_null( $string, array( 'slash_zero' => 'keep' ) );
- $string = wp_kses_normalize_entities( $string );
- $string = wp_kses_hook( $string, $allowed_html, $allowed_protocols );
+ $content = wp_kses_no_null( $content, array( 'slash_zero' => 'keep' ) );
+ $content = wp_kses_normalize_entities( $content );
+ $content = wp_kses_hook( $content, $allowed_html, $allowed_protocols );
- return wp_kses_split( $string, $allowed_html, $allowed_protocols );
+ return wp_kses_split( $content, $allowed_html, $allowed_protocols );
}
/**
@@ -728,43 +763,45 @@
*
* @since 4.2.3
*
- * @param string $string The 'whole' attribute, including name and value.
+ * @param string $attr The 'whole' attribute, including name and value.
* @param string $element The HTML element name to which the attribute belongs.
* @return string Filtered attribute.
*/
-function wp_kses_one_attr( $string, $element ) {
+function wp_kses_one_attr( $attr, $element ) {
$uris = wp_kses_uri_attributes();
$allowed_html = wp_kses_allowed_html( 'post' );
$allowed_protocols = wp_allowed_protocols();
- $string = wp_kses_no_null( $string, array( 'slash_zero' => 'keep' ) );
+ $attr = wp_kses_no_null( $attr, array( 'slash_zero' => 'keep' ) );
// Preserve leading and trailing whitespace.
$matches = array();
- preg_match( '/^\s*/', $string, $matches );
+ preg_match( '/^\s*/', $attr, $matches );
$lead = $matches[0];
- preg_match( '/\s*$/', $string, $matches );
+ preg_match( '/\s*$/', $attr, $matches );
$trail = $matches[0];
if ( empty( $trail ) ) {
- $string = substr( $string, strlen( $lead ) );
+ $attr = substr( $attr, strlen( $lead ) );
} else {
- $string = substr( $string, strlen( $lead ), -strlen( $trail ) );
+ $attr = substr( $attr, strlen( $lead ), -strlen( $trail ) );
}
// Parse attribute name and value from input.
- $split = preg_split( '/\s*=\s*/', $string, 2 );
+ $split = preg_split( '/\s*=\s*/', $attr, 2 );
$name = $split[0];
- if ( count( $split ) == 2 ) {
+ if ( count( $split ) === 2 ) {
$value = $split[1];
- // Remove quotes surrounding $value.
- // Also guarantee correct quoting in $string for this one attribute.
+ /*
+ * Remove quotes surrounding $value.
+ * Also guarantee correct quoting in $attr for this one attribute.
+ */
if ( '' === $value ) {
$quote = '';
} else {
$quote = $value[0];
}
if ( '"' === $quote || "'" === $quote ) {
- if ( substr( $value, -1 ) != $quote ) {
+ if ( ! str_ends_with( $value, $quote ) ) {
return '';
}
$value = substr( $value, 1, -1 );
@@ -780,18 +817,18 @@
$value = wp_kses_bad_protocol( $value, $allowed_protocols );
}
- $string = "$name=$quote$value$quote";
- $vless = 'n';
+ $attr = "$name=$quote$value$quote";
+ $vless = 'n';
} else {
$value = '';
$vless = 'y';
}
// Sanitize attribute by name.
- wp_kses_attr_check( $name, $value, $string, $vless, $element, $allowed_html );
+ wp_kses_attr_check( $name, $value, $attr, $vless, $element, $allowed_html );
// Restore whitespace.
- return $lead . $string . $trail;
+ return $lead . $attr . $trail;
}
/**
@@ -887,26 +924,26 @@
*
* @since 1.0.0
*
- * @param string $string Content to filter through KSES.
+ * @param string $content Content to filter through KSES.
* @param array[]|string $allowed_html An array of allowed HTML elements and attributes,
* or a context name such as 'post'. See wp_kses_allowed_html()
* for the list of accepted context names.
* @param string[] $allowed_protocols Array of allowed URL protocols.
* @return string Filtered content through {@see 'pre_kses'} hook.
*/
-function wp_kses_hook( $string, $allowed_html, $allowed_protocols ) {
+function wp_kses_hook( $content, $allowed_html, $allowed_protocols ) {
/**
* Filters content to be run through KSES.
*
* @since 2.3.0
*
- * @param string $string Content to filter through KSES.
+ * @param string $content Content to filter through KSES.
* @param array[]|string $allowed_html An array of allowed HTML elements and attributes,
* or a context name such as 'post'. See wp_kses_allowed_html()
* for the list of accepted context names.
* @param string[] $allowed_protocols Array of allowed URL protocols.
*/
- return apply_filters( 'pre_kses', $string, $allowed_html, $allowed_protocols );
+ return apply_filters( 'pre_kses', $content, $allowed_html, $allowed_protocols );
}
/**
@@ -926,25 +963,40 @@
* It also matches stray `>` characters.
*
* @since 1.0.0
+ * @since 6.6.0 Recognize additional forms of invalid HTML which convert into comments.
*
* @global array[]|string $pass_allowed_html An array of allowed HTML elements and attributes,
* or a context name such as 'post'.
* @global string[] $pass_allowed_protocols Array of allowed URL protocols.
*
- * @param string $string Content to filter.
+ * @param string $content Content to filter.
* @param array[]|string $allowed_html An array of allowed HTML elements and attributes,
* or a context name such as 'post'. See wp_kses_allowed_html()
* for the list of accepted context names.
* @param string[] $allowed_protocols Array of allowed URL protocols.
* @return string Content with fixed HTML tags
*/
-function wp_kses_split( $string, $allowed_html, $allowed_protocols ) {
+function wp_kses_split( $content, $allowed_html, $allowed_protocols ) {
global $pass_allowed_html, $pass_allowed_protocols;
$pass_allowed_html = $allowed_html;
$pass_allowed_protocols = $allowed_protocols;
- return preg_replace_callback( '%(<!--.*?(-->|$))|(<[^>]*(>|$)|>)%', '_wp_kses_split_callback', $string );
+ $token_pattern = <<<REGEX
+~
+ ( # Detect comments of various flavors before attempting to find tags.
+ (<!--.*?(-->|$)) # - Normative HTML comments.
+ |
+ </[^a-zA-Z][^>]*> # - Closing tags with invalid tag names.
+ |
+ <![^>]*> # - Invalid markup declaration nodes. Not all invalid nodes
+ # are matched so as to avoid breaking legacy behaviors.
+ )
+ |
+ (<[^>]*(>|$)|>) # Tag-like spans of text.
+~x
+REGEX;
+ return preg_replace_callback( $token_pattern, '_wp_kses_split_callback', $content );
}
/**
@@ -1008,13 +1060,13 @@
* or a context name such as 'post'.
* @global string[] $pass_allowed_protocols Array of allowed URL protocols.
*
- * @param array $match preg_replace regexp matches
+ * @param array $matches preg_replace regexp matches
* @return string
*/
-function _wp_kses_split_callback( $match ) {
+function _wp_kses_split_callback( $matches ) {
global $pass_allowed_html, $pass_allowed_protocols;
- return wp_kses_split2( $match[0], $pass_allowed_html, $pass_allowed_protocols );
+ return wp_kses_split2( $matches[0], $pass_allowed_html, $pass_allowed_protocols );
}
/**
@@ -1032,40 +1084,90 @@
* @access private
* @ignore
* @since 1.0.0
+ * @since 6.6.0 Recognize additional forms of invalid HTML which convert into comments.
*
- * @param string $string Content to filter.
+ * @param string $content Content to filter.
* @param array[]|string $allowed_html An array of allowed HTML elements and attributes,
* or a context name such as 'post'. See wp_kses_allowed_html()
* for the list of accepted context names.
* @param string[] $allowed_protocols Array of allowed URL protocols.
+ *
* @return string Fixed HTML element
*/
-function wp_kses_split2( $string, $allowed_html, $allowed_protocols ) {
- $string = wp_kses_stripslashes( $string );
+function wp_kses_split2( $content, $allowed_html, $allowed_protocols ) {
+ $content = wp_kses_stripslashes( $content );
- // It matched a ">" character.
- if ( '<' !== substr( $string, 0, 1 ) ) {
+ /*
+ * The regex pattern used to split HTML into chunks attempts
+ * to split on HTML token boundaries. This function should
+ * thus receive chunks that _either_ start with meaningful
+ * syntax tokens, like a tag `<div>` or a comment `<!-- ... -->`.
+ *
+ * If the first character of the `$content` chunk _isn't_ one
+ * of these syntax elements, which always starts with `<`, then
+ * the match had to be for the final alternation of `>`. In such
+ * case, it's probably standing on its own and could be encoded
+ * with a character reference to remove ambiguity.
+ *
+ * In other words, if this chunk isn't from a match of a syntax
+ * token, it's just a plaintext greater-than (`>`) sign.
+ */
+ if ( ! str_starts_with( $content, '<' ) ) {
return '>';
}
- // Allow HTML comments.
- if ( '<!--' === substr( $string, 0, 4 ) ) {
- $string = str_replace( array( '<!--', '-->' ), '', $string );
- while ( ( $newstring = wp_kses( $string, $allowed_html, $allowed_protocols ) ) != $string ) {
- $string = $newstring;
+ /*
+ * When certain invalid syntax constructs appear, the HTML parser
+ * shifts into what's called the "bogus comment state." This is a
+ * plaintext state that consumes everything until the nearest `>`
+ * and then transforms the entire span into an HTML comment.
+ *
+ * Preserve these comments and do not treat them like tags.
+ *
+ * @see https://html.spec.whatwg.org/#bogus-comment-state
+ */
+ if ( 1 === preg_match( '~^(?:</[^a-zA-Z][^>]*>|<![a-z][^>]*>)$~', $content ) ) {
+ /**
+ * Since the pattern matches `</…>` and also `<!…>`, this will
+ * preserve the type of the cleaned-up token in the output.
+ */
+ $opener = $content[1];
+ $content = substr( $content, 2, -1 );
+
+ do {
+ $prev = $content;
+ $content = wp_kses( $content, $allowed_html, $allowed_protocols );
+ } while ( $prev !== $content );
+
+ // Recombine the modified inner content with the original token structure.
+ return "<{$opener}{$content}>";
+ }
+
+ /*
+ * Normative HTML comments should be handled separately as their
+ * parsing rules differ from those for tags and text nodes.
+ */
+ if ( str_starts_with( $content, '<!--' ) ) {
+ $content = str_replace( array( '<!--', '-->' ), '', $content );
+
+ while ( ( $newstring = wp_kses( $content, $allowed_html, $allowed_protocols ) ) !== $content ) {
+ $content = $newstring;
}
- if ( '' === $string ) {
+
+ if ( '' === $content ) {
return '';
}
+
// Prevent multiple dashes in comments.
- $string = preg_replace( '/--+/', '-', $string );
+ $content = preg_replace( '/--+/', '-', $content );
// Prevent three dashes closing a comment.
- $string = preg_replace( '/-$/', '', $string );
- return "<!--{$string}-->";
+ $content = preg_replace( '/-$/', '', $content );
+
+ return "<!--{$content}-->";
}
// It's seriously malformed.
- if ( ! preg_match( '%^<\s*(/\s*)?([a-zA-Z0-9-]+)([^>]*)>?$%', $string, $matches ) ) {
+ if ( ! preg_match( '%^<\s*(/\s*)?([a-zA-Z0-9-]+)([^>]*)>?$%', $content, $matches ) ) {
return '';
}
@@ -1142,7 +1244,7 @@
// Check if there are attributes that are required.
$required_attrs = array_filter(
$allowed_html[ $element_low ],
- function( $required_attr_limits ) {
+ static function ( $required_attr_limits ) {
return isset( $required_attr_limits['required'] ) && true === $required_attr_limits['required'];
}
);
@@ -1222,11 +1324,10 @@
* `data-*` (not to be mixed with the HTML 4.0 `data` attribute, see
* https://www.w3.org/TR/html40/struct/objects.html#adef-data).
*
- * Note: the attribute name should only contain `A-Za-z0-9_-` chars,
- * double hyphens `--` are not accepted by WordPress.
+ * Note: the attribute name should only contain `A-Za-z0-9_-` chars.
*/
- if ( strpos( $name_low, 'data-' ) === 0 && ! empty( $allowed_attr['data-*'] )
- && preg_match( '/^data(?:-[a-z0-9_]+)+$/', $name_low, $match )
+ if ( str_starts_with( $name_low, 'data-' ) && ! empty( $allowed_attr['data-*'] )
+ && preg_match( '/^data-[a-z0-9_-]+$/', $name_low, $match )
) {
/*
* Add the whole attribute name to the allowed attributes and set any restrictions
@@ -1295,7 +1396,7 @@
// Loop through the whole attribute list.
- while ( strlen( $attr ) != 0 ) {
+ while ( strlen( $attr ) !== 0 ) {
$working = 0; // Was the last operation successful?
switch ( $mode ) {
@@ -1320,6 +1421,7 @@
if ( preg_match( '/^\s+/', $attr ) ) { // Valueless.
$working = 1;
$mode = 0;
+
if ( false === array_key_exists( $attrname, $attrarr ) ) {
$attrarr[ $attrname ] = array(
'name' => $attrname,
@@ -1328,6 +1430,7 @@
'vless' => 'y',
);
}
+
$attr = preg_replace( '/^\s+/', '', $attr );
}
@@ -1349,6 +1452,7 @@
'vless' => 'n',
);
}
+
$working = 1;
$mode = 0;
$attr = preg_replace( '/^"[^"]*"(\s+|$)/', '', $attr );
@@ -1370,6 +1474,7 @@
'vless' => 'n',
);
}
+
$working = 1;
$mode = 0;
$attr = preg_replace( "/^'[^']*'(\s+|$)/", '', $attr );
@@ -1391,6 +1496,7 @@
'vless' => 'n',
);
}
+
// We add quotes to conform to W3C's HTML spec.
$working = 1;
$mode = 0;
@@ -1400,15 +1506,17 @@
break;
} // End switch.
- if ( 0 == $working ) { // Not well-formed, remove and try again.
+ if ( 0 === $working ) { // Not well-formed, remove and try again.
$attr = wp_kses_html_error( $attr );
$mode = 0;
}
} // End while.
- if ( 1 == $mode && false === array_key_exists( $attrname, $attrarr ) ) {
- // Special case, for when the attribute list ends with a valueless
- // attribute like "selected".
+ if ( 1 === $mode && false === array_key_exists( $attrname, $attrarr ) ) {
+ /*
+ * Special case, for when the attribute list ends with a valueless
+ * attribute like "selected".
+ */
$attrarr[ $attrname ] = array(
'name' => $attrname,
'value' => '',
@@ -1488,34 +1596,37 @@
return array();
}
- // phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation
$regex =
- '(?:'
- . '[_a-zA-Z][-_a-zA-Z0-9:.]*' // Attribute name.
- . '|'
- . '\[\[?[^\[\]]+\]\]?' // Shortcode in the name position implies unfiltered_html.
- . ')'
- . '(?:' // Attribute value.
- . '\s*=\s*' // All values begin with '='.
- . '(?:'
- . '"[^"]*"' // Double-quoted.
- . '|'
- . "'[^']*'" // Single-quoted.
- . '|'
- . '[^\s"\']+' // Non-quoted.
- . '(?:\s|$)' // Must have a space.
- . ')'
- . '|'
- . '(?:\s|$)' // If attribute has no value, space is required.
- . ')'
- . '\s*'; // Trailing space is optional except as mentioned above.
- // phpcs:enable
+ '(?:
+ [_a-zA-Z][-_a-zA-Z0-9:.]* # Attribute name.
+ |
+ \[\[?[^\[\]]+\]\]? # Shortcode in the name position implies unfiltered_html.
+ )
+ (?: # Attribute value.
+ \s*=\s* # All values begin with "=".
+ (?:
+ "[^"]*" # Double-quoted.
+ |
+ \'[^\']*\' # Single-quoted.
+ |
+ [^\s"\']+ # Non-quoted.
+ (?:\s|$) # Must have a space.
+ )
+ |
+ (?:\s|$) # If attribute has no value, space is required.
+ )
+ \s* # Trailing space is optional except as mentioned above.
+ ';
- // Although it is possible to reduce this procedure to a single regexp,
- // we must run that regexp twice to get exactly the expected result.
+ /*
+ * Although it is possible to reduce this procedure to a single regexp,
+ * we must run that regexp twice to get exactly the expected result.
+ *
+ * Note: do NOT remove the `x` modifiers as they are essential for the above regex!
+ */
- $validation = "%^($regex)+$%";
- $extraction = "%$regex%";
+ $validation = "/^($regex)+$/x";
+ $extraction = "/$regex/x";
if ( 1 === preg_match( $validation, $attr ) ) {
preg_match_all( $extraction, $attr, $attrarr );
@@ -1605,7 +1716,7 @@
* If the given value is an "n" or an "N", the attribute must have a value.
*/
- if ( strtolower( $checkvalue ) != $vless ) {
+ if ( strtolower( $checkvalue ) !== $vless ) {
$ok = false;
}
break;
@@ -1646,24 +1757,33 @@
*
* @since 1.0.0
*
- * @param string $string Content to filter bad protocols from.
+ * @param string $content Content to filter bad protocols from.
* @param string[] $allowed_protocols Array of allowed URL protocols.
* @return string Filtered content.
*/
-function wp_kses_bad_protocol( $string, $allowed_protocols ) {
- $string = wp_kses_no_null( $string );
+function wp_kses_bad_protocol( $content, $allowed_protocols ) {
+ $content = wp_kses_no_null( $content );
+
+ // Short-circuit if the string starts with `https://` or `http://`. Most common cases.
+ if (
+ ( str_starts_with( $content, 'https://' ) && in_array( 'https', $allowed_protocols, true ) ) ||
+ ( str_starts_with( $content, 'http://' ) && in_array( 'http', $allowed_protocols, true ) )
+ ) {
+ return $content;
+ }
+
$iterations = 0;
do {
- $original_string = $string;
- $string = wp_kses_bad_protocol_once( $string, $allowed_protocols );
- } while ( $original_string != $string && ++$iterations < 6 );
+ $original_content = $content;
+ $content = wp_kses_bad_protocol_once( $content, $allowed_protocols );
+ } while ( $original_content !== $content && ++$iterations < 6 );
- if ( $original_string != $string ) {
+ if ( $original_content !== $content ) {
return '';
}
- return $string;
+ return $content;
}
/**
@@ -1673,21 +1793,21 @@
*
* @since 1.0.0
*
- * @param string $string Content to filter null characters from.
+ * @param string $content Content to filter null characters from.
* @param array $options Set 'slash_zero' => 'keep' when '\0' is allowed. Default is 'remove'.
* @return string Filtered content.
*/
-function wp_kses_no_null( $string, $options = null ) {
+function wp_kses_no_null( $content, $options = null ) {
if ( ! isset( $options['slash_zero'] ) ) {
$options = array( 'slash_zero' => 'remove' );
}
- $string = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $string );
+ $content = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $content );
if ( 'remove' === $options['slash_zero'] ) {
- $string = preg_replace( '/\\\\+0+/', '', $string );
+ $content = preg_replace( '/\\\\+0+/', '', $content );
}
- return $string;
+ return $content;
}
/**
@@ -1698,11 +1818,11 @@
*
* @since 1.0.0
*
- * @param string $string String to strip slashes from.
+ * @param string $content String to strip slashes from.
* @return string Fixed string with quoted slashes.
*/
-function wp_kses_stripslashes( $string ) {
- return preg_replace( '%\\\\"%', '"', $string );
+function wp_kses_stripslashes( $content ) {
+ return preg_replace( '%\\\\"%', '"', $content );
}
/**
@@ -1737,11 +1857,11 @@
*
* @since 1.0.0
*
- * @param string $string
+ * @param string $attr
* @return string
*/
-function wp_kses_html_error( $string ) {
- return preg_replace( '/^("[^"]*("|$)|\'[^\']*(\'|$)|\S)*\s*/', '', $string );
+function wp_kses_html_error( $attr ) {
+ return preg_replace( '/^("[^"]*("|$)|\'[^\']*(\'|$)|\S)*\s*/', '', $attr );
}
/**
@@ -1752,30 +1872,31 @@
*
* @since 1.0.0
*
- * @param string $string Content to check for bad protocols.
+ * @param string $content Content to check for bad protocols.
* @param string[] $allowed_protocols Array of allowed URL protocols.
* @param int $count Depth of call recursion to this function.
* @return string Sanitized content.
*/
-function wp_kses_bad_protocol_once( $string, $allowed_protocols, $count = 1 ) {
- $string = preg_replace( '/(�*58(?![;0-9])|�*3a(?![;a-f0-9]))/i', '$1;', $string );
- $string2 = preg_split( '/:|�*58;|�*3a;|:/i', $string, 2 );
- if ( isset( $string2[1] ) && ! preg_match( '%/\?%', $string2[0] ) ) {
- $string = trim( $string2[1] );
- $protocol = wp_kses_bad_protocol_once2( $string2[0], $allowed_protocols );
+function wp_kses_bad_protocol_once( $content, $allowed_protocols, $count = 1 ) {
+ $content = preg_replace( '/(�*58(?![;0-9])|�*3a(?![;a-f0-9]))/i', '$1;', $content );
+ $content2 = preg_split( '/:|�*58;|�*3a;|:/i', $content, 2 );
+
+ if ( isset( $content2[1] ) && ! preg_match( '%/\?%', $content2[0] ) ) {
+ $content = trim( $content2[1] );
+ $protocol = wp_kses_bad_protocol_once2( $content2[0], $allowed_protocols );
if ( 'feed:' === $protocol ) {
if ( $count > 2 ) {
return '';
}
- $string = wp_kses_bad_protocol_once( $string, $allowed_protocols, ++$count );
- if ( empty( $string ) ) {
- return $string;
+ $content = wp_kses_bad_protocol_once( $content, $allowed_protocols, ++$count );
+ if ( empty( $content ) ) {
+ return $content;
}
}
- $string = $protocol . $string;
+ $content = $protocol . $content;
}
- return $string;
+ return $content;
}
/**
@@ -1789,26 +1910,26 @@
* @ignore
* @since 1.0.0
*
- * @param string $string URI scheme to check against the list of allowed protocols.
+ * @param string $scheme URI scheme to check against the list of allowed protocols.
* @param string[] $allowed_protocols Array of allowed URL protocols.
* @return string Sanitized content.
*/
-function wp_kses_bad_protocol_once2( $string, $allowed_protocols ) {
- $string2 = wp_kses_decode_entities( $string );
- $string2 = preg_replace( '/\s/', '', $string2 );
- $string2 = wp_kses_no_null( $string2 );
- $string2 = strtolower( $string2 );
+function wp_kses_bad_protocol_once2( $scheme, $allowed_protocols ) {
+ $scheme = wp_kses_decode_entities( $scheme );
+ $scheme = preg_replace( '/\s/', '', $scheme );
+ $scheme = wp_kses_no_null( $scheme );
+ $scheme = strtolower( $scheme );
$allowed = false;
foreach ( (array) $allowed_protocols as $one_protocol ) {
- if ( strtolower( $one_protocol ) == $string2 ) {
+ if ( strtolower( $one_protocol ) === $scheme ) {
$allowed = true;
break;
}
}
if ( $allowed ) {
- return "$string2:";
+ return "$scheme:";
} else {
return '';
}
@@ -1826,25 +1947,25 @@
* @since 1.0.0
* @since 5.5.0 Added `$context` parameter.
*
- * @param string $string Content to normalize entities.
+ * @param string $content Content to normalize entities.
* @param string $context Context for normalization. Can be either 'html' or 'xml'.
* Default 'html'.
* @return string Content with normalized entities.
*/
-function wp_kses_normalize_entities( $string, $context = 'html' ) {
+function wp_kses_normalize_entities( $content, $context = 'html' ) {
// Disarm all entities by converting & to &
- $string = str_replace( '&', '&', $string );
+ $content = str_replace( '&', '&', $content );
// Change back the allowed entities in our list of allowed entities.
if ( 'xml' === $context ) {
- $string = preg_replace_callback( '/&([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_xml_named_entities', $string );
+ $content = preg_replace_callback( '/&([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_xml_named_entities', $content );
} else {
- $string = preg_replace_callback( '/&([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_named_entities', $string );
+ $content = preg_replace_callback( '/&([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_named_entities', $content );
}
- $string = preg_replace_callback( '/&#(0*[0-9]{1,7});/', 'wp_kses_normalize_entities2', $string );
- $string = preg_replace_callback( '/&#[Xx](0*[0-9A-Fa-f]{1,6});/', 'wp_kses_normalize_entities3', $string );
+ $content = preg_replace_callback( '/&#(0*[0-9]{1,7});/', 'wp_kses_normalize_entities2', $content );
+ $content = preg_replace_callback( '/&#[Xx](0*[0-9A-Fa-f]{1,6});/', 'wp_kses_normalize_entities3', $content );
- return $string;
+ return $content;
}
/**
@@ -1923,6 +2044,7 @@
}
$i = $matches[1];
+
if ( valid_unicode( $i ) ) {
$i = str_pad( ltrim( $i, '0' ), 3, '0', STR_PAD_LEFT );
$i = "&#$i;";
@@ -1952,6 +2074,7 @@
}
$hexchars = $matches[1];
+
return ( ! valid_unicode( hexdec( $hexchars ) ) ) ? "&#x$hexchars;" : '&#x' . ltrim( $hexchars, '0' ) . ';';
}
@@ -1964,10 +2087,13 @@
* @return bool Whether or not the codepoint is a valid Unicode codepoint.
*/
function valid_unicode( $i ) {
- return ( 0x9 == $i || 0xa == $i || 0xd == $i ||
- ( 0x20 <= $i && $i <= 0xd7ff ) ||
- ( 0xe000 <= $i && $i <= 0xfffd ) ||
- ( 0x10000 <= $i && $i <= 0x10ffff ) );
+ $i = (int) $i;
+
+ return ( 0x9 === $i || 0xa === $i || 0xd === $i ||
+ ( 0x20 <= $i && $i <= 0xd7ff ) ||
+ ( 0xe000 <= $i && $i <= 0xfffd ) ||
+ ( 0x10000 <= $i && $i <= 0x10ffff )
+ );
}
/**
@@ -1979,14 +2105,14 @@
*
* @since 1.0.0
*
- * @param string $string Content to change entities.
+ * @param string $content Content to change entities.
* @return string Content after decoded entities.
*/
-function wp_kses_decode_entities( $string ) {
- $string = preg_replace_callback( '/&#([0-9]+);/', '_wp_kses_decode_entities_chr', $string );
- $string = preg_replace_callback( '/&#[Xx]([0-9A-Fa-f]+);/', '_wp_kses_decode_entities_chr_hexdec', $string );
+function wp_kses_decode_entities( $content ) {
+ $content = preg_replace_callback( '/&#([0-9]+);/', '_wp_kses_decode_entities_chr', $content );
+ $content = preg_replace_callback( '/&#[Xx]([0-9A-Fa-f]+);/', '_wp_kses_decode_entities_chr_hexdec', $content );
- return $string;
+ return $content;
}
/**
@@ -1996,11 +2122,11 @@
* @access private
* @ignore
*
- * @param array $match preg match
+ * @param array $matches preg match
* @return string
*/
-function _wp_kses_decode_entities_chr( $match ) {
- return chr( $match[1] );
+function _wp_kses_decode_entities_chr( $matches ) {
+ return chr( $matches[1] );
}
/**
@@ -2010,11 +2136,11 @@
* @access private
* @ignore
*
- * @param array $match preg match
+ * @param array $matches preg match
* @return string
*/
-function _wp_kses_decode_entities_chr_hexdec( $match ) {
- return chr( hexdec( $match[1] ) );
+function _wp_kses_decode_entities_chr_hexdec( $matches ) {
+ return chr( hexdec( $matches[1] ) );
}
/**
@@ -2081,7 +2207,7 @@
) {
unset( $decoded_data['isGlobalStylesUserThemeJSON'] );
- $data_to_encode = WP_Theme_JSON::remove_insecure_properties( $decoded_data );
+ $data_to_encode = WP_Theme_JSON::remove_insecure_properties( $decoded_data, 'custom' );
$data_to_encode['isGlobalStylesUserThemeJSON'] = true;
return wp_slash( wp_json_encode( $data_to_encode ) );
@@ -2224,10 +2350,21 @@
* @since 5.1.0 Added support for `text-transform`.
* @since 5.2.0 Added support for `background-position` and `grid-template-columns`.
* @since 5.3.0 Added support for `grid`, `flex` and `column` layout properties.
- * Extend `background-*` support of individual properties.
+ * Extended `background-*` support for individual properties.
* @since 5.3.1 Added support for gradient backgrounds.
* @since 5.7.1 Added support for `object-position`.
* @since 5.8.0 Added support for `calc()` and `var()` values.
+ * @since 6.1.0 Added support for `min()`, `max()`, `minmax()`, `clamp()`,
+ * nested `var()` values, and assigning values to CSS variables.
+ * Added support for `object-fit`, `gap`, `column-gap`, `row-gap`, and `flex-wrap`.
+ * Extended `margin-*` and `padding-*` support for logical properties.
+ * @since 6.2.0 Added support for `aspect-ratio`, `position`, `top`, `right`, `bottom`, `left`,
+ * and `z-index` CSS properties.
+ * @since 6.3.0 Extended support for `filter` to accept a URL and added support for repeat().
+ * Added support for `box-shadow`.
+ * @since 6.4.0 Added support for `writing-mode`.
+ * @since 6.5.0 Added support for `background-repeat`.
+ * @since 6.6.0 Added support for `grid-column`, `grid-row`, and `container-type`.
*
* @param string $css A string of CSS rules.
* @param string $deprecated Not used.
@@ -2259,6 +2396,7 @@
'background-color',
'background-image',
'background-position',
+ 'background-repeat',
'background-size',
'background-attachment',
'background-blend-mode',
@@ -2333,12 +2471,20 @@
'margin-bottom',
'margin-left',
'margin-top',
+ 'margin-block-start',
+ 'margin-block-end',
+ 'margin-inline-start',
+ 'margin-inline-end',
'padding',
'padding-right',
'padding-bottom',
'padding-left',
'padding-top',
+ 'padding-block-start',
+ 'padding-block-end',
+ 'padding-inline-start',
+ 'padding-inline-end',
'flex',
'flex-basis',
@@ -2346,16 +2492,23 @@
'flex-flow',
'flex-grow',
'flex-shrink',
+ 'flex-wrap',
+
+ 'gap',
+ 'column-gap',
+ 'row-gap',
'grid-template-columns',
'grid-auto-columns',
'grid-column-start',
'grid-column-end',
+ 'grid-column',
'grid-column-gap',
'grid-template-rows',
'grid-auto-rows',
'grid-row-start',
'grid-row-end',
+ 'grid-row',
'grid-row-gap',
'grid-gap',
@@ -2371,9 +2524,24 @@
'direction',
'float',
'list-style-type',
+ 'object-fit',
'object-position',
'overflow',
'vertical-align',
+ 'writing-mode',
+
+ 'position',
+ 'top',
+ 'right',
+ 'bottom',
+ 'left',
+ 'z-index',
+ 'box-shadow',
+ 'aspect-ratio',
+ 'container-type',
+
+ // Custom CSS properties.
+ '--*',
)
);
@@ -2390,6 +2558,7 @@
'background-image',
'cursor',
+ 'filter',
'list-style',
'list-style-image',
@@ -2419,18 +2588,31 @@
$found = false;
$url_attr = false;
$gradient_attr = false;
+ $is_custom_var = false;
- if ( strpos( $css_item, ':' ) === false ) {
+ if ( ! str_contains( $css_item, ':' ) ) {
$found = true;
} else {
$parts = explode( ':', $css_item, 2 );
$css_selector = trim( $parts[0] );
+ // Allow assigning values to CSS variables.
+ if ( in_array( '--*', $allowed_attr, true ) && preg_match( '/^--[a-zA-Z0-9-_]+$/', $css_selector ) ) {
+ $allowed_attr[] = $css_selector;
+ $is_custom_var = true;
+ }
+
if ( in_array( $css_selector, $allowed_attr, true ) ) {
$found = true;
$url_attr = in_array( $css_selector, $css_url_data_types, true );
$gradient_attr = in_array( $css_selector, $css_gradient_data_types, true );
}
+
+ if ( $is_custom_var ) {
+ $css_value = trim( $parts[1] );
+ $url_attr = str_starts_with( $css_value, 'url(' );
+ $gradient_attr = str_contains( $css_value, '-gradient(' );
+ }
}
if ( $found && $url_attr ) {
@@ -2467,13 +2649,20 @@
}
if ( $found ) {
- // Allow CSS calc().
- $css_test_string = preg_replace( '/calc\(((?:\([^()]*\)?|[^()])*)\)/', '', $css_test_string );
- // Allow CSS var().
- $css_test_string = preg_replace( '/\(?var\(--[a-zA-Z0-9_-]*\)/', '', $css_test_string );
+ /*
+ * Allow CSS functions like var(), calc(), etc. by removing them from the test string.
+ * Nested functions and parentheses are also removed, so long as the parentheses are balanced.
+ */
+ $css_test_string = preg_replace(
+ '/\b(?:var|calc|min|max|minmax|clamp|repeat)(\((?:[^()]|(?1))*\))/',
+ '',
+ $css_test_string
+ );
- // Check for any CSS containing \ ( & } = or comments,
- // except for url(), calc(), or var() usage checked above.
+ /*
+ * Disallow CSS containing \ ( & } = or comments, except for within url(), var(), calc(), etc.
+ * which were removed from the test string above.
+ */
$allow_css = ! preg_match( '%[\\\(&=}]|/\*%', $css_test_string );
/**
@@ -2510,6 +2699,8 @@
* @since 3.5.0
* @since 5.0.0 Added support for `data-*` wildcard attributes.
* @since 6.0.0 Added `dir`, `lang`, and `xml:lang` to global attributes.
+ * @since 6.3.0 Added `aria-controls`, `aria-current`, and `aria-expanded` attributes.
+ * @since 6.4.0 Added `aria-live` and `hidden` attributes.
*
* @access private
* @ignore
@@ -2519,14 +2710,19 @@
*/
function _wp_add_global_attributes( $value ) {
$global_attributes = array(
+ 'aria-controls' => true,
+ 'aria-current' => true,
'aria-describedby' => true,
'aria-details' => true,
+ 'aria-expanded' => true,
+ 'aria-hidden' => true,
'aria-label' => true,
'aria-labelledby' => true,
- 'aria-hidden' => true,
+ 'aria-live' => true,
'class' => true,
'data-*' => true,
'dir' => true,
+ 'hidden' => true,
'id' => true,
'lang' => true,
'style' => true,