wp/wp-includes/kses.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    33 /**
    33 /**
    34  * Specifies the default allowable HTML tags.
    34  * Specifies the default allowable HTML tags.
    35  *
    35  *
    36  * Using `CUSTOM_TAGS` is not recommended and should be considered deprecated. The
    36  * Using `CUSTOM_TAGS` is not recommended and should be considered deprecated. The
    37  * {@see 'wp_kses_allowed_html'} filter is more powerful and supplies context.
    37  * {@see 'wp_kses_allowed_html'} filter is more powerful and supplies context.
       
    38  *
       
    39  * When using this constant, make sure to set all of these globals to arrays:
       
    40  *
       
    41  *  - `$allowedposttags`
       
    42  *  - `$allowedtags`
       
    43  *  - `$allowedentitynames`
       
    44  *  - `$allowedxmlentitynames`
    38  *
    45  *
    39  * @see wp_kses_allowed_html()
    46  * @see wp_kses_allowed_html()
    40  * @since 1.2.0
    47  * @since 1.2.0
    41  *
    48  *
    42  * @var array[]|false Array of default allowable HTML tags, or false to use the defaults.
    49  * @var array[]|false Array of default allowable HTML tags, or false to use the defaults.
   683 		'quot',
   690 		'quot',
   684 	);
   691 	);
   685 
   692 
   686 	$allowedposttags = array_map( '_wp_add_global_attributes', $allowedposttags );
   693 	$allowedposttags = array_map( '_wp_add_global_attributes', $allowedposttags );
   687 } else {
   694 } else {
       
   695 	$required_kses_globals = array(
       
   696 		'allowedposttags',
       
   697 		'allowedtags',
       
   698 		'allowedentitynames',
       
   699 		'allowedxmlentitynames',
       
   700 	);
       
   701 	$missing_kses_globals  = array();
       
   702 
       
   703 	foreach ( $required_kses_globals as $global_name ) {
       
   704 		if ( ! isset( $GLOBALS[ $global_name ] ) || ! is_array( $GLOBALS[ $global_name ] ) ) {
       
   705 			$missing_kses_globals[] = '<code>$' . $global_name . '</code>';
       
   706 		}
       
   707 	}
       
   708 
       
   709 	if ( $missing_kses_globals ) {
       
   710 		_doing_it_wrong(
       
   711 			'wp_kses_allowed_html',
       
   712 			sprintf(
       
   713 				/* translators: 1: CUSTOM_TAGS, 2: Global variable names. */
       
   714 				__( 'When using the %1$s constant, make sure to set these globals to an array: %2$s.' ),
       
   715 				'<code>CUSTOM_TAGS</code>',
       
   716 				implode( ', ', $missing_kses_globals )
       
   717 			),
       
   718 			'6.2.0'
       
   719 		);
       
   720 	}
       
   721 
   688 	$allowedtags     = wp_kses_array_lc( $allowedtags );
   722 	$allowedtags     = wp_kses_array_lc( $allowedtags );
   689 	$allowedposttags = wp_kses_array_lc( $allowedposttags );
   723 	$allowedposttags = wp_kses_array_lc( $allowedposttags );
   690 }
   724 }
   691 
   725 
   692 /**
   726 /**
   700  * @see wp_kses_post() for specifically filtering post content and fields.
   734  * @see wp_kses_post() for specifically filtering post content and fields.
   701  * @see wp_allowed_protocols() for the default allowed protocols in link URLs.
   735  * @see wp_allowed_protocols() for the default allowed protocols in link URLs.
   702  *
   736  *
   703  * @since 1.0.0
   737  * @since 1.0.0
   704  *
   738  *
   705  * @param string         $string            Text content to filter.
   739  * @param string         $content           Text content to filter.
   706  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
   740  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
   707  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
   741  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
   708  *                                          for the list of accepted context names.
   742  *                                          for the list of accepted context names.
   709  * @param string[]       $allowed_protocols Array of allowed URL protocols.
   743  * @param string[]       $allowed_protocols Optional. Array of allowed URL protocols.
       
   744  *                                          Defaults to the result of wp_allowed_protocols().
   710  * @return string Filtered content containing only the allowed HTML.
   745  * @return string Filtered content containing only the allowed HTML.
   711  */
   746  */
   712 function wp_kses( $string, $allowed_html, $allowed_protocols = array() ) {
   747 function wp_kses( $content, $allowed_html, $allowed_protocols = array() ) {
   713 	if ( empty( $allowed_protocols ) ) {
   748 	if ( empty( $allowed_protocols ) ) {
   714 		$allowed_protocols = wp_allowed_protocols();
   749 		$allowed_protocols = wp_allowed_protocols();
   715 	}
   750 	}
   716 
   751 
   717 	$string = wp_kses_no_null( $string, array( 'slash_zero' => 'keep' ) );
   752 	$content = wp_kses_no_null( $content, array( 'slash_zero' => 'keep' ) );
   718 	$string = wp_kses_normalize_entities( $string );
   753 	$content = wp_kses_normalize_entities( $content );
   719 	$string = wp_kses_hook( $string, $allowed_html, $allowed_protocols );
   754 	$content = wp_kses_hook( $content, $allowed_html, $allowed_protocols );
   720 
   755 
   721 	return wp_kses_split( $string, $allowed_html, $allowed_protocols );
   756 	return wp_kses_split( $content, $allowed_html, $allowed_protocols );
   722 }
   757 }
   723 
   758 
   724 /**
   759 /**
   725  * Filters one HTML attribute and ensures its value is allowed.
   760  * Filters one HTML attribute and ensures its value is allowed.
   726  *
   761  *
   727  * This function can escape data in some situations where `wp_kses()` must strip the whole attribute.
   762  * This function can escape data in some situations where `wp_kses()` must strip the whole attribute.
   728  *
   763  *
   729  * @since 4.2.3
   764  * @since 4.2.3
   730  *
   765  *
   731  * @param string $string  The 'whole' attribute, including name and value.
   766  * @param string $attr    The 'whole' attribute, including name and value.
   732  * @param string $element The HTML element name to which the attribute belongs.
   767  * @param string $element The HTML element name to which the attribute belongs.
   733  * @return string Filtered attribute.
   768  * @return string Filtered attribute.
   734  */
   769  */
   735 function wp_kses_one_attr( $string, $element ) {
   770 function wp_kses_one_attr( $attr, $element ) {
   736 	$uris              = wp_kses_uri_attributes();
   771 	$uris              = wp_kses_uri_attributes();
   737 	$allowed_html      = wp_kses_allowed_html( 'post' );
   772 	$allowed_html      = wp_kses_allowed_html( 'post' );
   738 	$allowed_protocols = wp_allowed_protocols();
   773 	$allowed_protocols = wp_allowed_protocols();
   739 	$string            = wp_kses_no_null( $string, array( 'slash_zero' => 'keep' ) );
   774 	$attr              = wp_kses_no_null( $attr, array( 'slash_zero' => 'keep' ) );
   740 
   775 
   741 	// Preserve leading and trailing whitespace.
   776 	// Preserve leading and trailing whitespace.
   742 	$matches = array();
   777 	$matches = array();
   743 	preg_match( '/^\s*/', $string, $matches );
   778 	preg_match( '/^\s*/', $attr, $matches );
   744 	$lead = $matches[0];
   779 	$lead = $matches[0];
   745 	preg_match( '/\s*$/', $string, $matches );
   780 	preg_match( '/\s*$/', $attr, $matches );
   746 	$trail = $matches[0];
   781 	$trail = $matches[0];
   747 	if ( empty( $trail ) ) {
   782 	if ( empty( $trail ) ) {
   748 		$string = substr( $string, strlen( $lead ) );
   783 		$attr = substr( $attr, strlen( $lead ) );
   749 	} else {
   784 	} else {
   750 		$string = substr( $string, strlen( $lead ), -strlen( $trail ) );
   785 		$attr = substr( $attr, strlen( $lead ), -strlen( $trail ) );
   751 	}
   786 	}
   752 
   787 
   753 	// Parse attribute name and value from input.
   788 	// Parse attribute name and value from input.
   754 	$split = preg_split( '/\s*=\s*/', $string, 2 );
   789 	$split = preg_split( '/\s*=\s*/', $attr, 2 );
   755 	$name  = $split[0];
   790 	$name  = $split[0];
   756 	if ( count( $split ) == 2 ) {
   791 	if ( count( $split ) === 2 ) {
   757 		$value = $split[1];
   792 		$value = $split[1];
   758 
   793 
   759 		// Remove quotes surrounding $value.
   794 		/*
   760 		// Also guarantee correct quoting in $string for this one attribute.
   795 		 * Remove quotes surrounding $value.
       
   796 		 * Also guarantee correct quoting in $attr for this one attribute.
       
   797 		 */
   761 		if ( '' === $value ) {
   798 		if ( '' === $value ) {
   762 			$quote = '';
   799 			$quote = '';
   763 		} else {
   800 		} else {
   764 			$quote = $value[0];
   801 			$quote = $value[0];
   765 		}
   802 		}
   766 		if ( '"' === $quote || "'" === $quote ) {
   803 		if ( '"' === $quote || "'" === $quote ) {
   767 			if ( substr( $value, -1 ) != $quote ) {
   804 			if ( ! str_ends_with( $value, $quote ) ) {
   768 				return '';
   805 				return '';
   769 			}
   806 			}
   770 			$value = substr( $value, 1, -1 );
   807 			$value = substr( $value, 1, -1 );
   771 		} else {
   808 		} else {
   772 			$quote = '"';
   809 			$quote = '"';
   778 		// Sanitize URI values.
   815 		// Sanitize URI values.
   779 		if ( in_array( strtolower( $name ), $uris, true ) ) {
   816 		if ( in_array( strtolower( $name ), $uris, true ) ) {
   780 			$value = wp_kses_bad_protocol( $value, $allowed_protocols );
   817 			$value = wp_kses_bad_protocol( $value, $allowed_protocols );
   781 		}
   818 		}
   782 
   819 
   783 		$string = "$name=$quote$value$quote";
   820 		$attr  = "$name=$quote$value$quote";
   784 		$vless  = 'n';
   821 		$vless = 'n';
   785 	} else {
   822 	} else {
   786 		$value = '';
   823 		$value = '';
   787 		$vless = 'y';
   824 		$vless = 'y';
   788 	}
   825 	}
   789 
   826 
   790 	// Sanitize attribute by name.
   827 	// Sanitize attribute by name.
   791 	wp_kses_attr_check( $name, $value, $string, $vless, $element, $allowed_html );
   828 	wp_kses_attr_check( $name, $value, $attr, $vless, $element, $allowed_html );
   792 
   829 
   793 	// Restore whitespace.
   830 	// Restore whitespace.
   794 	return $lead . $string . $trail;
   831 	return $lead . $attr . $trail;
   795 }
   832 }
   796 
   833 
   797 /**
   834 /**
   798  * Returns an array of allowed HTML tags and attributes for a given context.
   835  * Returns an array of allowed HTML tags and attributes for a given context.
   799  *
   836  *
   885  * There is currently only one KSES WordPress hook, {@see 'pre_kses'}, and it is called here.
   922  * There is currently only one KSES WordPress hook, {@see 'pre_kses'}, and it is called here.
   886  * All parameters are passed to the hooks and expected to receive a string.
   923  * All parameters are passed to the hooks and expected to receive a string.
   887  *
   924  *
   888  * @since 1.0.0
   925  * @since 1.0.0
   889  *
   926  *
   890  * @param string         $string            Content to filter through KSES.
   927  * @param string         $content           Content to filter through KSES.
   891  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
   928  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
   892  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
   929  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
   893  *                                          for the list of accepted context names.
   930  *                                          for the list of accepted context names.
   894  * @param string[]       $allowed_protocols Array of allowed URL protocols.
   931  * @param string[]       $allowed_protocols Array of allowed URL protocols.
   895  * @return string Filtered content through {@see 'pre_kses'} hook.
   932  * @return string Filtered content through {@see 'pre_kses'} hook.
   896  */
   933  */
   897 function wp_kses_hook( $string, $allowed_html, $allowed_protocols ) {
   934 function wp_kses_hook( $content, $allowed_html, $allowed_protocols ) {
   898 	/**
   935 	/**
   899 	 * Filters content to be run through KSES.
   936 	 * Filters content to be run through KSES.
   900 	 *
   937 	 *
   901 	 * @since 2.3.0
   938 	 * @since 2.3.0
   902 	 *
   939 	 *
   903 	 * @param string         $string            Content to filter through KSES.
   940 	 * @param string         $content           Content to filter through KSES.
   904 	 * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
   941 	 * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
   905 	 *                                          or a context name such as 'post'. See wp_kses_allowed_html()
   942 	 *                                          or a context name such as 'post'. See wp_kses_allowed_html()
   906 	 *                                          for the list of accepted context names.
   943 	 *                                          for the list of accepted context names.
   907 	 * @param string[]       $allowed_protocols Array of allowed URL protocols.
   944 	 * @param string[]       $allowed_protocols Array of allowed URL protocols.
   908 	 */
   945 	 */
   909 	return apply_filters( 'pre_kses', $string, $allowed_html, $allowed_protocols );
   946 	return apply_filters( 'pre_kses', $content, $allowed_html, $allowed_protocols );
   910 }
   947 }
   911 
   948 
   912 /**
   949 /**
   913  * Returns the version number of KSES.
   950  * Returns the version number of KSES.
   914  *
   951  *
   924  * Searches for HTML tags, no matter how malformed.
   961  * Searches for HTML tags, no matter how malformed.
   925  *
   962  *
   926  * It also matches stray `>` characters.
   963  * It also matches stray `>` characters.
   927  *
   964  *
   928  * @since 1.0.0
   965  * @since 1.0.0
       
   966  * @since 6.6.0 Recognize additional forms of invalid HTML which convert into comments.
   929  *
   967  *
   930  * @global array[]|string $pass_allowed_html      An array of allowed HTML elements and attributes,
   968  * @global array[]|string $pass_allowed_html      An array of allowed HTML elements and attributes,
   931  *                                                or a context name such as 'post'.
   969  *                                                or a context name such as 'post'.
   932  * @global string[]       $pass_allowed_protocols Array of allowed URL protocols.
   970  * @global string[]       $pass_allowed_protocols Array of allowed URL protocols.
   933  *
   971  *
   934  * @param string         $string            Content to filter.
   972  * @param string         $content           Content to filter.
   935  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
   973  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
   936  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
   974  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
   937  *                                          for the list of accepted context names.
   975  *                                          for the list of accepted context names.
   938  * @param string[]       $allowed_protocols Array of allowed URL protocols.
   976  * @param string[]       $allowed_protocols Array of allowed URL protocols.
   939  * @return string Content with fixed HTML tags
   977  * @return string Content with fixed HTML tags
   940  */
   978  */
   941 function wp_kses_split( $string, $allowed_html, $allowed_protocols ) {
   979 function wp_kses_split( $content, $allowed_html, $allowed_protocols ) {
   942 	global $pass_allowed_html, $pass_allowed_protocols;
   980 	global $pass_allowed_html, $pass_allowed_protocols;
   943 
   981 
   944 	$pass_allowed_html      = $allowed_html;
   982 	$pass_allowed_html      = $allowed_html;
   945 	$pass_allowed_protocols = $allowed_protocols;
   983 	$pass_allowed_protocols = $allowed_protocols;
   946 
   984 
   947 	return preg_replace_callback( '%(<!--.*?(-->|$))|(<[^>]*(>|$)|>)%', '_wp_kses_split_callback', $string );
   985 	$token_pattern = <<<REGEX
       
   986 ~
       
   987 	(                      # Detect comments of various flavors before attempting to find tags.
       
   988 		(<!--.*?(-->|$))   #  - Normative HTML comments.
       
   989 		|
       
   990 		</[^a-zA-Z][^>]*>  #  - Closing tags with invalid tag names.
       
   991 		|
       
   992 		<![^>]*>           #  - Invalid markup declaration nodes. Not all invalid nodes
       
   993 		                   #    are matched so as to avoid breaking legacy behaviors.
       
   994 	)
       
   995 	|
       
   996 	(<[^>]*(>|$)|>)        # Tag-like spans of text.
       
   997 ~x
       
   998 REGEX;
       
   999 	return preg_replace_callback( $token_pattern, '_wp_kses_split_callback', $content );
   948 }
  1000 }
   949 
  1001 
   950 /**
  1002 /**
   951  * Returns an array of HTML attribute names whose value contains a URL.
  1003  * Returns an array of HTML attribute names whose value contains a URL.
   952  *
  1004  *
  1006  *
  1058  *
  1007  * @global array[]|string $pass_allowed_html      An array of allowed HTML elements and attributes,
  1059  * @global array[]|string $pass_allowed_html      An array of allowed HTML elements and attributes,
  1008  *                                                or a context name such as 'post'.
  1060  *                                                or a context name such as 'post'.
  1009  * @global string[]       $pass_allowed_protocols Array of allowed URL protocols.
  1061  * @global string[]       $pass_allowed_protocols Array of allowed URL protocols.
  1010  *
  1062  *
  1011  * @param array $match preg_replace regexp matches
  1063  * @param array $matches preg_replace regexp matches
  1012  * @return string
  1064  * @return string
  1013  */
  1065  */
  1014 function _wp_kses_split_callback( $match ) {
  1066 function _wp_kses_split_callback( $matches ) {
  1015 	global $pass_allowed_html, $pass_allowed_protocols;
  1067 	global $pass_allowed_html, $pass_allowed_protocols;
  1016 
  1068 
  1017 	return wp_kses_split2( $match[0], $pass_allowed_html, $pass_allowed_protocols );
  1069 	return wp_kses_split2( $matches[0], $pass_allowed_html, $pass_allowed_protocols );
  1018 }
  1070 }
  1019 
  1071 
  1020 /**
  1072 /**
  1021  * Callback for `wp_kses_split()` for fixing malformed HTML tags.
  1073  * Callback for `wp_kses_split()` for fixing malformed HTML tags.
  1022  *
  1074  *
  1030  * completed, will be returned.
  1082  * completed, will be returned.
  1031  *
  1083  *
  1032  * @access private
  1084  * @access private
  1033  * @ignore
  1085  * @ignore
  1034  * @since 1.0.0
  1086  * @since 1.0.0
  1035  *
  1087  * @since 6.6.0 Recognize additional forms of invalid HTML which convert into comments.
  1036  * @param string         $string            Content to filter.
  1088  *
       
  1089  * @param string         $content           Content to filter.
  1037  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
  1090  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
  1038  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
  1091  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
  1039  *                                          for the list of accepted context names.
  1092  *                                          for the list of accepted context names.
  1040  * @param string[]       $allowed_protocols Array of allowed URL protocols.
  1093  * @param string[]       $allowed_protocols Array of allowed URL protocols.
       
  1094  *
  1041  * @return string Fixed HTML element
  1095  * @return string Fixed HTML element
  1042  */
  1096  */
  1043 function wp_kses_split2( $string, $allowed_html, $allowed_protocols ) {
  1097 function wp_kses_split2( $content, $allowed_html, $allowed_protocols ) {
  1044 	$string = wp_kses_stripslashes( $string );
  1098 	$content = wp_kses_stripslashes( $content );
  1045 
  1099 
  1046 	// It matched a ">" character.
  1100 	/*
  1047 	if ( '<' !== substr( $string, 0, 1 ) ) {
  1101 	 * The regex pattern used to split HTML into chunks attempts
       
  1102 	 * to split on HTML token boundaries. This function should
       
  1103 	 * thus receive chunks that _either_ start with meaningful
       
  1104 	 * syntax tokens, like a tag `<div>` or a comment `<!-- ... -->`.
       
  1105 	 *
       
  1106 	 * If the first character of the `$content` chunk _isn't_ one
       
  1107 	 * of these syntax elements, which always starts with `<`, then
       
  1108 	 * the match had to be for the final alternation of `>`. In such
       
  1109 	 * case, it's probably standing on its own and could be encoded
       
  1110 	 * with a character reference to remove ambiguity.
       
  1111 	 *
       
  1112 	 * In other words, if this chunk isn't from a match of a syntax
       
  1113 	 * token, it's just a plaintext greater-than (`>`) sign.
       
  1114 	 */
       
  1115 	if ( ! str_starts_with( $content, '<' ) ) {
  1048 		return '&gt;';
  1116 		return '&gt;';
  1049 	}
  1117 	}
  1050 
  1118 
  1051 	// Allow HTML comments.
  1119 	/*
  1052 	if ( '<!--' === substr( $string, 0, 4 ) ) {
  1120 	 * When certain invalid syntax constructs appear, the HTML parser
  1053 		$string = str_replace( array( '<!--', '-->' ), '', $string );
  1121 	 * shifts into what's called the "bogus comment state." This is a
  1054 		while ( ( $newstring = wp_kses( $string, $allowed_html, $allowed_protocols ) ) != $string ) {
  1122 	 * plaintext state that consumes everything until the nearest `>`
  1055 			$string = $newstring;
  1123 	 * and then transforms the entire span into an HTML comment.
       
  1124 	 *
       
  1125 	 * Preserve these comments and do not treat them like tags.
       
  1126 	 *
       
  1127 	 * @see https://html.spec.whatwg.org/#bogus-comment-state
       
  1128 	 */
       
  1129 	if ( 1 === preg_match( '~^(?:</[^a-zA-Z][^>]*>|<![a-z][^>]*>)$~', $content ) ) {
       
  1130 		/**
       
  1131 		 * Since the pattern matches `</…>` and also `<!…>`, this will
       
  1132 		 * preserve the type of the cleaned-up token in the output.
       
  1133 		 */
       
  1134 		$opener  = $content[1];
       
  1135 		$content = substr( $content, 2, -1 );
       
  1136 
       
  1137 		do {
       
  1138 			$prev    = $content;
       
  1139 			$content = wp_kses( $content, $allowed_html, $allowed_protocols );
       
  1140 		} while ( $prev !== $content );
       
  1141 
       
  1142 		// Recombine the modified inner content with the original token structure.
       
  1143 		return "<{$opener}{$content}>";
       
  1144 	}
       
  1145 
       
  1146 	/*
       
  1147 	 * Normative HTML comments should be handled separately as their
       
  1148 	 * parsing rules differ from those for tags and text nodes.
       
  1149 	 */
       
  1150 	if ( str_starts_with( $content, '<!--' ) ) {
       
  1151 		$content = str_replace( array( '<!--', '-->' ), '', $content );
       
  1152 
       
  1153 		while ( ( $newstring = wp_kses( $content, $allowed_html, $allowed_protocols ) ) !== $content ) {
       
  1154 			$content = $newstring;
  1056 		}
  1155 		}
  1057 		if ( '' === $string ) {
  1156 
       
  1157 		if ( '' === $content ) {
  1058 			return '';
  1158 			return '';
  1059 		}
  1159 		}
       
  1160 
  1060 		// Prevent multiple dashes in comments.
  1161 		// Prevent multiple dashes in comments.
  1061 		$string = preg_replace( '/--+/', '-', $string );
  1162 		$content = preg_replace( '/--+/', '-', $content );
  1062 		// Prevent three dashes closing a comment.
  1163 		// Prevent three dashes closing a comment.
  1063 		$string = preg_replace( '/-$/', '', $string );
  1164 		$content = preg_replace( '/-$/', '', $content );
  1064 		return "<!--{$string}-->";
  1165 
       
  1166 		return "<!--{$content}-->";
  1065 	}
  1167 	}
  1066 
  1168 
  1067 	// It's seriously malformed.
  1169 	// It's seriously malformed.
  1068 	if ( ! preg_match( '%^<\s*(/\s*)?([a-zA-Z0-9-]+)([^>]*)>?$%', $string, $matches ) ) {
  1170 	if ( ! preg_match( '%^<\s*(/\s*)?([a-zA-Z0-9-]+)([^>]*)>?$%', $content, $matches ) ) {
  1069 		return '';
  1171 		return '';
  1070 	}
  1172 	}
  1071 
  1173 
  1072 	$slash    = trim( $matches[1] );
  1174 	$slash    = trim( $matches[1] );
  1073 	$elem     = $matches[2];
  1175 	$elem     = $matches[2];
  1140 	$attrarr = wp_kses_hair( $attr, $allowed_protocols );
  1242 	$attrarr = wp_kses_hair( $attr, $allowed_protocols );
  1141 
  1243 
  1142 	// Check if there are attributes that are required.
  1244 	// Check if there are attributes that are required.
  1143 	$required_attrs = array_filter(
  1245 	$required_attrs = array_filter(
  1144 		$allowed_html[ $element_low ],
  1246 		$allowed_html[ $element_low ],
  1145 		function( $required_attr_limits ) {
  1247 		static function ( $required_attr_limits ) {
  1146 			return isset( $required_attr_limits['required'] ) && true === $required_attr_limits['required'];
  1248 			return isset( $required_attr_limits['required'] ) && true === $required_attr_limits['required'];
  1147 		}
  1249 		}
  1148 	);
  1250 	);
  1149 
  1251 
  1150 	/*
  1252 	/*
  1220 		 *
  1322 		 *
  1221 		 * When specifying `$allowed_html`, the attribute name should be set as
  1323 		 * When specifying `$allowed_html`, the attribute name should be set as
  1222 		 * `data-*` (not to be mixed with the HTML 4.0 `data` attribute, see
  1324 		 * `data-*` (not to be mixed with the HTML 4.0 `data` attribute, see
  1223 		 * https://www.w3.org/TR/html40/struct/objects.html#adef-data).
  1325 		 * https://www.w3.org/TR/html40/struct/objects.html#adef-data).
  1224 		 *
  1326 		 *
  1225 		 * Note: the attribute name should only contain `A-Za-z0-9_-` chars,
  1327 		 * Note: the attribute name should only contain `A-Za-z0-9_-` chars.
  1226 		 * double hyphens `--` are not accepted by WordPress.
       
  1227 		 */
  1328 		 */
  1228 		if ( strpos( $name_low, 'data-' ) === 0 && ! empty( $allowed_attr['data-*'] )
  1329 		if ( str_starts_with( $name_low, 'data-' ) && ! empty( $allowed_attr['data-*'] )
  1229 			&& preg_match( '/^data(?:-[a-z0-9_]+)+$/', $name_low, $match )
  1330 			&& preg_match( '/^data-[a-z0-9_-]+$/', $name_low, $match )
  1230 		) {
  1331 		) {
  1231 			/*
  1332 			/*
  1232 			 * Add the whole attribute name to the allowed attributes and set any restrictions
  1333 			 * Add the whole attribute name to the allowed attributes and set any restrictions
  1233 			 * for the `data-*` attribute values for the current element.
  1334 			 * for the `data-*` attribute values for the current element.
  1234 			 */
  1335 			 */
  1293 	$attrname = '';
  1394 	$attrname = '';
  1294 	$uris     = wp_kses_uri_attributes();
  1395 	$uris     = wp_kses_uri_attributes();
  1295 
  1396 
  1296 	// Loop through the whole attribute list.
  1397 	// Loop through the whole attribute list.
  1297 
  1398 
  1298 	while ( strlen( $attr ) != 0 ) {
  1399 	while ( strlen( $attr ) !== 0 ) {
  1299 		$working = 0; // Was the last operation successful?
  1400 		$working = 0; // Was the last operation successful?
  1300 
  1401 
  1301 		switch ( $mode ) {
  1402 		switch ( $mode ) {
  1302 			case 0:
  1403 			case 0:
  1303 				if ( preg_match( '/^([_a-zA-Z][-_a-zA-Z0-9:.]*)/', $attr, $match ) ) {
  1404 				if ( preg_match( '/^([_a-zA-Z][-_a-zA-Z0-9:.]*)/', $attr, $match ) ) {
  1318 				}
  1419 				}
  1319 
  1420 
  1320 				if ( preg_match( '/^\s+/', $attr ) ) { // Valueless.
  1421 				if ( preg_match( '/^\s+/', $attr ) ) { // Valueless.
  1321 					$working = 1;
  1422 					$working = 1;
  1322 					$mode    = 0;
  1423 					$mode    = 0;
       
  1424 
  1323 					if ( false === array_key_exists( $attrname, $attrarr ) ) {
  1425 					if ( false === array_key_exists( $attrname, $attrarr ) ) {
  1324 						$attrarr[ $attrname ] = array(
  1426 						$attrarr[ $attrname ] = array(
  1325 							'name'  => $attrname,
  1427 							'name'  => $attrname,
  1326 							'value' => '',
  1428 							'value' => '',
  1327 							'whole' => $attrname,
  1429 							'whole' => $attrname,
  1328 							'vless' => 'y',
  1430 							'vless' => 'y',
  1329 						);
  1431 						);
  1330 					}
  1432 					}
       
  1433 
  1331 					$attr = preg_replace( '/^\s+/', '', $attr );
  1434 					$attr = preg_replace( '/^\s+/', '', $attr );
  1332 				}
  1435 				}
  1333 
  1436 
  1334 				break;
  1437 				break;
  1335 
  1438 
  1347 							'value' => $thisval,
  1450 							'value' => $thisval,
  1348 							'whole' => "$attrname=\"$thisval\"",
  1451 							'whole' => "$attrname=\"$thisval\"",
  1349 							'vless' => 'n',
  1452 							'vless' => 'n',
  1350 						);
  1453 						);
  1351 					}
  1454 					}
       
  1455 
  1352 					$working = 1;
  1456 					$working = 1;
  1353 					$mode    = 0;
  1457 					$mode    = 0;
  1354 					$attr    = preg_replace( '/^"[^"]*"(\s+|$)/', '', $attr );
  1458 					$attr    = preg_replace( '/^"[^"]*"(\s+|$)/', '', $attr );
  1355 					break;
  1459 					break;
  1356 				}
  1460 				}
  1368 							'value' => $thisval,
  1472 							'value' => $thisval,
  1369 							'whole' => "$attrname='$thisval'",
  1473 							'whole' => "$attrname='$thisval'",
  1370 							'vless' => 'n',
  1474 							'vless' => 'n',
  1371 						);
  1475 						);
  1372 					}
  1476 					}
       
  1477 
  1373 					$working = 1;
  1478 					$working = 1;
  1374 					$mode    = 0;
  1479 					$mode    = 0;
  1375 					$attr    = preg_replace( "/^'[^']*'(\s+|$)/", '', $attr );
  1480 					$attr    = preg_replace( "/^'[^']*'(\s+|$)/", '', $attr );
  1376 					break;
  1481 					break;
  1377 				}
  1482 				}
  1389 							'value' => $thisval,
  1494 							'value' => $thisval,
  1390 							'whole' => "$attrname=\"$thisval\"",
  1495 							'whole' => "$attrname=\"$thisval\"",
  1391 							'vless' => 'n',
  1496 							'vless' => 'n',
  1392 						);
  1497 						);
  1393 					}
  1498 					}
       
  1499 
  1394 					// We add quotes to conform to W3C's HTML spec.
  1500 					// We add quotes to conform to W3C's HTML spec.
  1395 					$working = 1;
  1501 					$working = 1;
  1396 					$mode    = 0;
  1502 					$mode    = 0;
  1397 					$attr    = preg_replace( "%^[^\s\"']+(\s+|$)%", '', $attr );
  1503 					$attr    = preg_replace( "%^[^\s\"']+(\s+|$)%", '', $attr );
  1398 				}
  1504 				}
  1399 
  1505 
  1400 				break;
  1506 				break;
  1401 		} // End switch.
  1507 		} // End switch.
  1402 
  1508 
  1403 		if ( 0 == $working ) { // Not well-formed, remove and try again.
  1509 		if ( 0 === $working ) { // Not well-formed, remove and try again.
  1404 			$attr = wp_kses_html_error( $attr );
  1510 			$attr = wp_kses_html_error( $attr );
  1405 			$mode = 0;
  1511 			$mode = 0;
  1406 		}
  1512 		}
  1407 	} // End while.
  1513 	} // End while.
  1408 
  1514 
  1409 	if ( 1 == $mode && false === array_key_exists( $attrname, $attrarr ) ) {
  1515 	if ( 1 === $mode && false === array_key_exists( $attrname, $attrarr ) ) {
  1410 		// Special case, for when the attribute list ends with a valueless
  1516 		/*
  1411 		// attribute like "selected".
  1517 		 * Special case, for when the attribute list ends with a valueless
       
  1518 		 * attribute like "selected".
       
  1519 		 */
  1412 		$attrarr[ $attrname ] = array(
  1520 		$attrarr[ $attrname ] = array(
  1413 			'name'  => $attrname,
  1521 			'name'  => $attrname,
  1414 			'value' => '',
  1522 			'value' => '',
  1415 			'whole' => $attrname,
  1523 			'whole' => $attrname,
  1416 			'vless' => 'y',
  1524 			'vless' => 'y',
  1486 function wp_kses_hair_parse( $attr ) {
  1594 function wp_kses_hair_parse( $attr ) {
  1487 	if ( '' === $attr ) {
  1595 	if ( '' === $attr ) {
  1488 		return array();
  1596 		return array();
  1489 	}
  1597 	}
  1490 
  1598 
  1491 	// phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation
       
  1492 	$regex =
  1599 	$regex =
  1493 		'(?:'
  1600 		'(?:
  1494 		.     '[_a-zA-Z][-_a-zA-Z0-9:.]*' // Attribute name.
  1601 				[_a-zA-Z][-_a-zA-Z0-9:.]* # Attribute name.
  1495 		. '|'
  1602 			|
  1496 		.     '\[\[?[^\[\]]+\]\]?'        // Shortcode in the name position implies unfiltered_html.
  1603 				\[\[?[^\[\]]+\]\]?        # Shortcode in the name position implies unfiltered_html.
  1497 		. ')'
  1604 		)
  1498 		. '(?:'               // Attribute value.
  1605 		(?:                               # Attribute value.
  1499 		.     '\s*=\s*'       // All values begin with '='.
  1606 			\s*=\s*                       # All values begin with "=".
  1500 		.     '(?:'
  1607 			(?:
  1501 		.         '"[^"]*"'   // Double-quoted.
  1608 				"[^"]*"                   # Double-quoted.
  1502 		.     '|'
  1609 			|
  1503 		.         "'[^']*'"   // Single-quoted.
  1610 				\'[^\']*\'                # Single-quoted.
  1504 		.     '|'
  1611 			|
  1505 		.         '[^\s"\']+' // Non-quoted.
  1612 				[^\s"\']+                 # Non-quoted.
  1506 		.         '(?:\s|$)'  // Must have a space.
  1613 				(?:\s|$)                  # Must have a space.
  1507 		.     ')'
  1614 			)
  1508 		. '|'
  1615 		|
  1509 		.     '(?:\s|$)'      // If attribute has no value, space is required.
  1616 			(?:\s|$)                      # If attribute has no value, space is required.
  1510 		. ')'
  1617 		)
  1511 		. '\s*';              // Trailing space is optional except as mentioned above.
  1618 		\s*                               # Trailing space is optional except as mentioned above.
  1512 	// phpcs:enable
  1619 		';
  1513 
  1620 
  1514 	// Although it is possible to reduce this procedure to a single regexp,
  1621 	/*
  1515 	// we must run that regexp twice to get exactly the expected result.
  1622 	 * Although it is possible to reduce this procedure to a single regexp,
  1516 
  1623 	 * we must run that regexp twice to get exactly the expected result.
  1517 	$validation = "%^($regex)+$%";
  1624 	 *
  1518 	$extraction = "%$regex%";
  1625 	 * Note: do NOT remove the `x` modifiers as they are essential for the above regex!
       
  1626 	 */
       
  1627 
       
  1628 	$validation = "/^($regex)+$/x";
       
  1629 	$extraction = "/$regex/x";
  1519 
  1630 
  1520 	if ( 1 === preg_match( $validation, $attr ) ) {
  1631 	if ( 1 === preg_match( $validation, $attr ) ) {
  1521 		preg_match_all( $extraction, $attr, $attrarr );
  1632 		preg_match_all( $extraction, $attr, $attrarr );
  1522 		return $attrarr[0];
  1633 		return $attrarr[0];
  1523 	} else {
  1634 	} else {
  1603 			 * (like `<a href="blah">`) or not (`<option selected>`). If the given value
  1714 			 * (like `<a href="blah">`) or not (`<option selected>`). If the given value
  1604 			 * is a "y" or a "Y", the attribute must not have a value.
  1715 			 * is a "y" or a "Y", the attribute must not have a value.
  1605 			 * If the given value is an "n" or an "N", the attribute must have a value.
  1716 			 * If the given value is an "n" or an "N", the attribute must have a value.
  1606 			 */
  1717 			 */
  1607 
  1718 
  1608 			if ( strtolower( $checkvalue ) != $vless ) {
  1719 			if ( strtolower( $checkvalue ) !== $vless ) {
  1609 				$ok = false;
  1720 				$ok = false;
  1610 			}
  1721 			}
  1611 			break;
  1722 			break;
  1612 
  1723 
  1613 		case 'values':
  1724 		case 'values':
  1644  * understand HTML entities. It does its work recursively, so it won't be
  1755  * understand HTML entities. It does its work recursively, so it won't be
  1645  * fooled by a string like `javascript:javascript:alert(57)`.
  1756  * fooled by a string like `javascript:javascript:alert(57)`.
  1646  *
  1757  *
  1647  * @since 1.0.0
  1758  * @since 1.0.0
  1648  *
  1759  *
  1649  * @param string   $string            Content to filter bad protocols from.
  1760  * @param string   $content           Content to filter bad protocols from.
  1650  * @param string[] $allowed_protocols Array of allowed URL protocols.
  1761  * @param string[] $allowed_protocols Array of allowed URL protocols.
  1651  * @return string Filtered content.
  1762  * @return string Filtered content.
  1652  */
  1763  */
  1653 function wp_kses_bad_protocol( $string, $allowed_protocols ) {
  1764 function wp_kses_bad_protocol( $content, $allowed_protocols ) {
  1654 	$string     = wp_kses_no_null( $string );
  1765 	$content = wp_kses_no_null( $content );
       
  1766 
       
  1767 	// Short-circuit if the string starts with `https://` or `http://`. Most common cases.
       
  1768 	if (
       
  1769 		( str_starts_with( $content, 'https://' ) && in_array( 'https', $allowed_protocols, true ) ) ||
       
  1770 		( str_starts_with( $content, 'http://' ) && in_array( 'http', $allowed_protocols, true ) )
       
  1771 	) {
       
  1772 		return $content;
       
  1773 	}
       
  1774 
  1655 	$iterations = 0;
  1775 	$iterations = 0;
  1656 
  1776 
  1657 	do {
  1777 	do {
  1658 		$original_string = $string;
  1778 		$original_content = $content;
  1659 		$string          = wp_kses_bad_protocol_once( $string, $allowed_protocols );
  1779 		$content          = wp_kses_bad_protocol_once( $content, $allowed_protocols );
  1660 	} while ( $original_string != $string && ++$iterations < 6 );
  1780 	} while ( $original_content !== $content && ++$iterations < 6 );
  1661 
  1781 
  1662 	if ( $original_string != $string ) {
  1782 	if ( $original_content !== $content ) {
  1663 		return '';
  1783 		return '';
  1664 	}
  1784 	}
  1665 
  1785 
  1666 	return $string;
  1786 	return $content;
  1667 }
  1787 }
  1668 
  1788 
  1669 /**
  1789 /**
  1670  * Removes any invalid control characters in a text string.
  1790  * Removes any invalid control characters in a text string.
  1671  *
  1791  *
  1672  * Also removes any instance of the `\0` string.
  1792  * Also removes any instance of the `\0` string.
  1673  *
  1793  *
  1674  * @since 1.0.0
  1794  * @since 1.0.0
  1675  *
  1795  *
  1676  * @param string $string  Content to filter null characters from.
  1796  * @param string $content Content to filter null characters from.
  1677  * @param array  $options Set 'slash_zero' => 'keep' when '\0' is allowed. Default is 'remove'.
  1797  * @param array  $options Set 'slash_zero' => 'keep' when '\0' is allowed. Default is 'remove'.
  1678  * @return string Filtered content.
  1798  * @return string Filtered content.
  1679  */
  1799  */
  1680 function wp_kses_no_null( $string, $options = null ) {
  1800 function wp_kses_no_null( $content, $options = null ) {
  1681 	if ( ! isset( $options['slash_zero'] ) ) {
  1801 	if ( ! isset( $options['slash_zero'] ) ) {
  1682 		$options = array( 'slash_zero' => 'remove' );
  1802 		$options = array( 'slash_zero' => 'remove' );
  1683 	}
  1803 	}
  1684 
  1804 
  1685 	$string = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $string );
  1805 	$content = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $content );
  1686 	if ( 'remove' === $options['slash_zero'] ) {
  1806 	if ( 'remove' === $options['slash_zero'] ) {
  1687 		$string = preg_replace( '/\\\\+0+/', '', $string );
  1807 		$content = preg_replace( '/\\\\+0+/', '', $content );
  1688 	}
  1808 	}
  1689 
  1809 
  1690 	return $string;
  1810 	return $content;
  1691 }
  1811 }
  1692 
  1812 
  1693 /**
  1813 /**
  1694  * Strips slashes from in front of quotes.
  1814  * Strips slashes from in front of quotes.
  1695  *
  1815  *
  1696  * This function changes the character sequence `\"` to just `"`. It leaves all other
  1816  * This function changes the character sequence `\"` to just `"`. It leaves all other
  1697  * slashes alone. The quoting from `preg_replace(//e)` requires this.
  1817  * slashes alone. The quoting from `preg_replace(//e)` requires this.
  1698  *
  1818  *
  1699  * @since 1.0.0
  1819  * @since 1.0.0
  1700  *
  1820  *
  1701  * @param string $string String to strip slashes from.
  1821  * @param string $content String to strip slashes from.
  1702  * @return string Fixed string with quoted slashes.
  1822  * @return string Fixed string with quoted slashes.
  1703  */
  1823  */
  1704 function wp_kses_stripslashes( $string ) {
  1824 function wp_kses_stripslashes( $content ) {
  1705 	return preg_replace( '%\\\\"%', '"', $string );
  1825 	return preg_replace( '%\\\\"%', '"', $content );
  1706 }
  1826 }
  1707 
  1827 
  1708 /**
  1828 /**
  1709  * Converts the keys of an array to lowercase.
  1829  * Converts the keys of an array to lowercase.
  1710  *
  1830  *
  1735  * The general plan is to remove everything to and including some whitespace,
  1855  * The general plan is to remove everything to and including some whitespace,
  1736  * but it deals with quotes and apostrophes as well.
  1856  * but it deals with quotes and apostrophes as well.
  1737  *
  1857  *
  1738  * @since 1.0.0
  1858  * @since 1.0.0
  1739  *
  1859  *
  1740  * @param string $string
  1860  * @param string $attr
  1741  * @return string
  1861  * @return string
  1742  */
  1862  */
  1743 function wp_kses_html_error( $string ) {
  1863 function wp_kses_html_error( $attr ) {
  1744 	return preg_replace( '/^("[^"]*("|$)|\'[^\']*(\'|$)|\S)*\s*/', '', $string );
  1864 	return preg_replace( '/^("[^"]*("|$)|\'[^\']*(\'|$)|\S)*\s*/', '', $attr );
  1745 }
  1865 }
  1746 
  1866 
  1747 /**
  1867 /**
  1748  * Sanitizes content from bad protocols and other characters.
  1868  * Sanitizes content from bad protocols and other characters.
  1749  *
  1869  *
  1750  * This function searches for URL protocols at the beginning of the string, while
  1870  * This function searches for URL protocols at the beginning of the string, while
  1751  * handling whitespace and HTML entities.
  1871  * handling whitespace and HTML entities.
  1752  *
  1872  *
  1753  * @since 1.0.0
  1873  * @since 1.0.0
  1754  *
  1874  *
  1755  * @param string   $string            Content to check for bad protocols.
  1875  * @param string   $content           Content to check for bad protocols.
  1756  * @param string[] $allowed_protocols Array of allowed URL protocols.
  1876  * @param string[] $allowed_protocols Array of allowed URL protocols.
  1757  * @param int      $count             Depth of call recursion to this function.
  1877  * @param int      $count             Depth of call recursion to this function.
  1758  * @return string Sanitized content.
  1878  * @return string Sanitized content.
  1759  */
  1879  */
  1760 function wp_kses_bad_protocol_once( $string, $allowed_protocols, $count = 1 ) {
  1880 function wp_kses_bad_protocol_once( $content, $allowed_protocols, $count = 1 ) {
  1761 	$string  = preg_replace( '/(&#0*58(?![;0-9])|&#x0*3a(?![;a-f0-9]))/i', '$1;', $string );
  1881 	$content  = preg_replace( '/(&#0*58(?![;0-9])|&#x0*3a(?![;a-f0-9]))/i', '$1;', $content );
  1762 	$string2 = preg_split( '/:|&#0*58;|&#x0*3a;|&colon;/i', $string, 2 );
  1882 	$content2 = preg_split( '/:|&#0*58;|&#x0*3a;|&colon;/i', $content, 2 );
  1763 	if ( isset( $string2[1] ) && ! preg_match( '%/\?%', $string2[0] ) ) {
  1883 
  1764 		$string   = trim( $string2[1] );
  1884 	if ( isset( $content2[1] ) && ! preg_match( '%/\?%', $content2[0] ) ) {
  1765 		$protocol = wp_kses_bad_protocol_once2( $string2[0], $allowed_protocols );
  1885 		$content  = trim( $content2[1] );
       
  1886 		$protocol = wp_kses_bad_protocol_once2( $content2[0], $allowed_protocols );
  1766 		if ( 'feed:' === $protocol ) {
  1887 		if ( 'feed:' === $protocol ) {
  1767 			if ( $count > 2 ) {
  1888 			if ( $count > 2 ) {
  1768 				return '';
  1889 				return '';
  1769 			}
  1890 			}
  1770 			$string = wp_kses_bad_protocol_once( $string, $allowed_protocols, ++$count );
  1891 			$content = wp_kses_bad_protocol_once( $content, $allowed_protocols, ++$count );
  1771 			if ( empty( $string ) ) {
  1892 			if ( empty( $content ) ) {
  1772 				return $string;
  1893 				return $content;
  1773 			}
  1894 			}
  1774 		}
  1895 		}
  1775 		$string = $protocol . $string;
  1896 		$content = $protocol . $content;
  1776 	}
  1897 	}
  1777 
  1898 
  1778 	return $string;
  1899 	return $content;
  1779 }
  1900 }
  1780 
  1901 
  1781 /**
  1902 /**
  1782  * Callback for `wp_kses_bad_protocol_once()` regular expression.
  1903  * Callback for `wp_kses_bad_protocol_once()` regular expression.
  1783  *
  1904  *
  1787  *
  1908  *
  1788  * @access private
  1909  * @access private
  1789  * @ignore
  1910  * @ignore
  1790  * @since 1.0.0
  1911  * @since 1.0.0
  1791  *
  1912  *
  1792  * @param string   $string            URI scheme to check against the list of allowed protocols.
  1913  * @param string   $scheme            URI scheme to check against the list of allowed protocols.
  1793  * @param string[] $allowed_protocols Array of allowed URL protocols.
  1914  * @param string[] $allowed_protocols Array of allowed URL protocols.
  1794  * @return string Sanitized content.
  1915  * @return string Sanitized content.
  1795  */
  1916  */
  1796 function wp_kses_bad_protocol_once2( $string, $allowed_protocols ) {
  1917 function wp_kses_bad_protocol_once2( $scheme, $allowed_protocols ) {
  1797 	$string2 = wp_kses_decode_entities( $string );
  1918 	$scheme = wp_kses_decode_entities( $scheme );
  1798 	$string2 = preg_replace( '/\s/', '', $string2 );
  1919 	$scheme = preg_replace( '/\s/', '', $scheme );
  1799 	$string2 = wp_kses_no_null( $string2 );
  1920 	$scheme = wp_kses_no_null( $scheme );
  1800 	$string2 = strtolower( $string2 );
  1921 	$scheme = strtolower( $scheme );
  1801 
  1922 
  1802 	$allowed = false;
  1923 	$allowed = false;
  1803 	foreach ( (array) $allowed_protocols as $one_protocol ) {
  1924 	foreach ( (array) $allowed_protocols as $one_protocol ) {
  1804 		if ( strtolower( $one_protocol ) == $string2 ) {
  1925 		if ( strtolower( $one_protocol ) === $scheme ) {
  1805 			$allowed = true;
  1926 			$allowed = true;
  1806 			break;
  1927 			break;
  1807 		}
  1928 		}
  1808 	}
  1929 	}
  1809 
  1930 
  1810 	if ( $allowed ) {
  1931 	if ( $allowed ) {
  1811 		return "$string2:";
  1932 		return "$scheme:";
  1812 	} else {
  1933 	} else {
  1813 		return '';
  1934 		return '';
  1814 	}
  1935 	}
  1815 }
  1936 }
  1816 
  1937 
  1824  * example, `AT&T&hellip;&#XYZZY;` is converted to `AT&amp;T…&amp;#XYZZY;`.
  1945  * example, `AT&T&hellip;&#XYZZY;` is converted to `AT&amp;T…&amp;#XYZZY;`.
  1825  *
  1946  *
  1826  * @since 1.0.0
  1947  * @since 1.0.0
  1827  * @since 5.5.0 Added `$context` parameter.
  1948  * @since 5.5.0 Added `$context` parameter.
  1828  *
  1949  *
  1829  * @param string $string  Content to normalize entities.
  1950  * @param string $content Content to normalize entities.
  1830  * @param string $context Context for normalization. Can be either 'html' or 'xml'.
  1951  * @param string $context Context for normalization. Can be either 'html' or 'xml'.
  1831  *                        Default 'html'.
  1952  *                        Default 'html'.
  1832  * @return string Content with normalized entities.
  1953  * @return string Content with normalized entities.
  1833  */
  1954  */
  1834 function wp_kses_normalize_entities( $string, $context = 'html' ) {
  1955 function wp_kses_normalize_entities( $content, $context = 'html' ) {
  1835 	// Disarm all entities by converting & to &amp;
  1956 	// Disarm all entities by converting & to &amp;
  1836 	$string = str_replace( '&', '&amp;', $string );
  1957 	$content = str_replace( '&', '&amp;', $content );
  1837 
  1958 
  1838 	// Change back the allowed entities in our list of allowed entities.
  1959 	// Change back the allowed entities in our list of allowed entities.
  1839 	if ( 'xml' === $context ) {
  1960 	if ( 'xml' === $context ) {
  1840 		$string = preg_replace_callback( '/&amp;([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_xml_named_entities', $string );
  1961 		$content = preg_replace_callback( '/&amp;([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_xml_named_entities', $content );
  1841 	} else {
  1962 	} else {
  1842 		$string = preg_replace_callback( '/&amp;([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_named_entities', $string );
  1963 		$content = preg_replace_callback( '/&amp;([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_named_entities', $content );
  1843 	}
  1964 	}
  1844 	$string = preg_replace_callback( '/&amp;#(0*[0-9]{1,7});/', 'wp_kses_normalize_entities2', $string );
  1965 	$content = preg_replace_callback( '/&amp;#(0*[0-9]{1,7});/', 'wp_kses_normalize_entities2', $content );
  1845 	$string = preg_replace_callback( '/&amp;#[Xx](0*[0-9A-Fa-f]{1,6});/', 'wp_kses_normalize_entities3', $string );
  1966 	$content = preg_replace_callback( '/&amp;#[Xx](0*[0-9A-Fa-f]{1,6});/', 'wp_kses_normalize_entities3', $content );
  1846 
  1967 
  1847 	return $string;
  1968 	return $content;
  1848 }
  1969 }
  1849 
  1970 
  1850 /**
  1971 /**
  1851  * Callback for `wp_kses_normalize_entities()` regular expression.
  1972  * Callback for `wp_kses_normalize_entities()` regular expression.
  1852  *
  1973  *
  1921 	if ( empty( $matches[1] ) ) {
  2042 	if ( empty( $matches[1] ) ) {
  1922 		return '';
  2043 		return '';
  1923 	}
  2044 	}
  1924 
  2045 
  1925 	$i = $matches[1];
  2046 	$i = $matches[1];
       
  2047 
  1926 	if ( valid_unicode( $i ) ) {
  2048 	if ( valid_unicode( $i ) ) {
  1927 		$i = str_pad( ltrim( $i, '0' ), 3, '0', STR_PAD_LEFT );
  2049 		$i = str_pad( ltrim( $i, '0' ), 3, '0', STR_PAD_LEFT );
  1928 		$i = "&#$i;";
  2050 		$i = "&#$i;";
  1929 	} else {
  2051 	} else {
  1930 		$i = "&amp;#$i;";
  2052 		$i = "&amp;#$i;";
  1950 	if ( empty( $matches[1] ) ) {
  2072 	if ( empty( $matches[1] ) ) {
  1951 		return '';
  2073 		return '';
  1952 	}
  2074 	}
  1953 
  2075 
  1954 	$hexchars = $matches[1];
  2076 	$hexchars = $matches[1];
       
  2077 
  1955 	return ( ! valid_unicode( hexdec( $hexchars ) ) ) ? "&amp;#x$hexchars;" : '&#x' . ltrim( $hexchars, '0' ) . ';';
  2078 	return ( ! valid_unicode( hexdec( $hexchars ) ) ) ? "&amp;#x$hexchars;" : '&#x' . ltrim( $hexchars, '0' ) . ';';
  1956 }
  2079 }
  1957 
  2080 
  1958 /**
  2081 /**
  1959  * Determines if a Unicode codepoint is valid.
  2082  * Determines if a Unicode codepoint is valid.
  1962  *
  2085  *
  1963  * @param int $i Unicode codepoint.
  2086  * @param int $i Unicode codepoint.
  1964  * @return bool Whether or not the codepoint is a valid Unicode codepoint.
  2087  * @return bool Whether or not the codepoint is a valid Unicode codepoint.
  1965  */
  2088  */
  1966 function valid_unicode( $i ) {
  2089 function valid_unicode( $i ) {
  1967 	return ( 0x9 == $i || 0xa == $i || 0xd == $i ||
  2090 	$i = (int) $i;
  1968 			( 0x20 <= $i && $i <= 0xd7ff ) ||
  2091 
  1969 			( 0xe000 <= $i && $i <= 0xfffd ) ||
  2092 	return ( 0x9 === $i || 0xa === $i || 0xd === $i ||
  1970 			( 0x10000 <= $i && $i <= 0x10ffff ) );
  2093 		( 0x20 <= $i && $i <= 0xd7ff ) ||
       
  2094 		( 0xe000 <= $i && $i <= 0xfffd ) ||
       
  2095 		( 0x10000 <= $i && $i <= 0x10ffff )
       
  2096 	);
  1971 }
  2097 }
  1972 
  2098 
  1973 /**
  2099 /**
  1974  * Converts all numeric HTML entities to their named counterparts.
  2100  * Converts all numeric HTML entities to their named counterparts.
  1975  *
  2101  *
  1977  * It doesn't do anything with named entities like `&auml;`, but we don't
  2103  * It doesn't do anything with named entities like `&auml;`, but we don't
  1978  * need them in the allowed URL protocols system anyway.
  2104  * need them in the allowed URL protocols system anyway.
  1979  *
  2105  *
  1980  * @since 1.0.0
  2106  * @since 1.0.0
  1981  *
  2107  *
  1982  * @param string $string Content to change entities.
  2108  * @param string $content Content to change entities.
  1983  * @return string Content after decoded entities.
  2109  * @return string Content after decoded entities.
  1984  */
  2110  */
  1985 function wp_kses_decode_entities( $string ) {
  2111 function wp_kses_decode_entities( $content ) {
  1986 	$string = preg_replace_callback( '/&#([0-9]+);/', '_wp_kses_decode_entities_chr', $string );
  2112 	$content = preg_replace_callback( '/&#([0-9]+);/', '_wp_kses_decode_entities_chr', $content );
  1987 	$string = preg_replace_callback( '/&#[Xx]([0-9A-Fa-f]+);/', '_wp_kses_decode_entities_chr_hexdec', $string );
  2113 	$content = preg_replace_callback( '/&#[Xx]([0-9A-Fa-f]+);/', '_wp_kses_decode_entities_chr_hexdec', $content );
  1988 
  2114 
  1989 	return $string;
  2115 	return $content;
  1990 }
  2116 }
  1991 
  2117 
  1992 /**
  2118 /**
  1993  * Regex callback for `wp_kses_decode_entities()`.
  2119  * Regex callback for `wp_kses_decode_entities()`.
  1994  *
  2120  *
  1995  * @since 2.9.0
  2121  * @since 2.9.0
  1996  * @access private
  2122  * @access private
  1997  * @ignore
  2123  * @ignore
  1998  *
  2124  *
  1999  * @param array $match preg match
  2125  * @param array $matches preg match
  2000  * @return string
  2126  * @return string
  2001  */
  2127  */
  2002 function _wp_kses_decode_entities_chr( $match ) {
  2128 function _wp_kses_decode_entities_chr( $matches ) {
  2003 	return chr( $match[1] );
  2129 	return chr( $matches[1] );
  2004 }
  2130 }
  2005 
  2131 
  2006 /**
  2132 /**
  2007  * Regex callback for `wp_kses_decode_entities()`.
  2133  * Regex callback for `wp_kses_decode_entities()`.
  2008  *
  2134  *
  2009  * @since 2.9.0
  2135  * @since 2.9.0
  2010  * @access private
  2136  * @access private
  2011  * @ignore
  2137  * @ignore
  2012  *
  2138  *
  2013  * @param array $match preg match
  2139  * @param array $matches preg match
  2014  * @return string
  2140  * @return string
  2015  */
  2141  */
  2016 function _wp_kses_decode_entities_chr_hexdec( $match ) {
  2142 function _wp_kses_decode_entities_chr_hexdec( $matches ) {
  2017 	return chr( hexdec( $match[1] ) );
  2143 	return chr( hexdec( $matches[1] ) );
  2018 }
  2144 }
  2019 
  2145 
  2020 /**
  2146 /**
  2021  * Sanitize content with allowed HTML KSES rules.
  2147  * Sanitize content with allowed HTML KSES rules.
  2022  *
  2148  *
  2079 		isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) &&
  2205 		isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) &&
  2080 		$decoded_data['isGlobalStylesUserThemeJSON']
  2206 		$decoded_data['isGlobalStylesUserThemeJSON']
  2081 	) {
  2207 	) {
  2082 		unset( $decoded_data['isGlobalStylesUserThemeJSON'] );
  2208 		unset( $decoded_data['isGlobalStylesUserThemeJSON'] );
  2083 
  2209 
  2084 		$data_to_encode = WP_Theme_JSON::remove_insecure_properties( $decoded_data );
  2210 		$data_to_encode = WP_Theme_JSON::remove_insecure_properties( $decoded_data, 'custom' );
  2085 
  2211 
  2086 		$data_to_encode['isGlobalStylesUserThemeJSON'] = true;
  2212 		$data_to_encode['isGlobalStylesUserThemeJSON'] = true;
  2087 		return wp_slash( wp_json_encode( $data_to_encode ) );
  2213 		return wp_slash( wp_json_encode( $data_to_encode ) );
  2088 	}
  2214 	}
  2089 	return $data;
  2215 	return $data;
  2222  * @since 4.6.0 Added support for `list-style-type`.
  2348  * @since 4.6.0 Added support for `list-style-type`.
  2223  * @since 5.0.0 Added support for `background-image`.
  2349  * @since 5.0.0 Added support for `background-image`.
  2224  * @since 5.1.0 Added support for `text-transform`.
  2350  * @since 5.1.0 Added support for `text-transform`.
  2225  * @since 5.2.0 Added support for `background-position` and `grid-template-columns`.
  2351  * @since 5.2.0 Added support for `background-position` and `grid-template-columns`.
  2226  * @since 5.3.0 Added support for `grid`, `flex` and `column` layout properties.
  2352  * @since 5.3.0 Added support for `grid`, `flex` and `column` layout properties.
  2227  *              Extend `background-*` support of individual properties.
  2353  *              Extended `background-*` support for individual properties.
  2228  * @since 5.3.1 Added support for gradient backgrounds.
  2354  * @since 5.3.1 Added support for gradient backgrounds.
  2229  * @since 5.7.1 Added support for `object-position`.
  2355  * @since 5.7.1 Added support for `object-position`.
  2230  * @since 5.8.0 Added support for `calc()` and `var()` values.
  2356  * @since 5.8.0 Added support for `calc()` and `var()` values.
       
  2357  * @since 6.1.0 Added support for `min()`, `max()`, `minmax()`, `clamp()`,
       
  2358  *              nested `var()` values, and assigning values to CSS variables.
       
  2359  *              Added support for `object-fit`, `gap`, `column-gap`, `row-gap`, and `flex-wrap`.
       
  2360  *              Extended `margin-*` and `padding-*` support for logical properties.
       
  2361  * @since 6.2.0 Added support for `aspect-ratio`, `position`, `top`, `right`, `bottom`, `left`,
       
  2362  *              and `z-index` CSS properties.
       
  2363  * @since 6.3.0 Extended support for `filter` to accept a URL and added support for repeat().
       
  2364  *              Added support for `box-shadow`.
       
  2365  * @since 6.4.0 Added support for `writing-mode`.
       
  2366  * @since 6.5.0 Added support for `background-repeat`.
       
  2367  * @since 6.6.0 Added support for `grid-column`, `grid-row`, and `container-type`.
  2231  *
  2368  *
  2232  * @param string $css        A string of CSS rules.
  2369  * @param string $css        A string of CSS rules.
  2233  * @param string $deprecated Not used.
  2370  * @param string $deprecated Not used.
  2234  * @return string Filtered string of CSS rules.
  2371  * @return string Filtered string of CSS rules.
  2235  */
  2372  */
  2257 		array(
  2394 		array(
  2258 			'background',
  2395 			'background',
  2259 			'background-color',
  2396 			'background-color',
  2260 			'background-image',
  2397 			'background-image',
  2261 			'background-position',
  2398 			'background-position',
       
  2399 			'background-repeat',
  2262 			'background-size',
  2400 			'background-size',
  2263 			'background-attachment',
  2401 			'background-attachment',
  2264 			'background-blend-mode',
  2402 			'background-blend-mode',
  2265 
  2403 
  2266 			'border',
  2404 			'border',
  2331 			'margin',
  2469 			'margin',
  2332 			'margin-right',
  2470 			'margin-right',
  2333 			'margin-bottom',
  2471 			'margin-bottom',
  2334 			'margin-left',
  2472 			'margin-left',
  2335 			'margin-top',
  2473 			'margin-top',
       
  2474 			'margin-block-start',
       
  2475 			'margin-block-end',
       
  2476 			'margin-inline-start',
       
  2477 			'margin-inline-end',
  2336 
  2478 
  2337 			'padding',
  2479 			'padding',
  2338 			'padding-right',
  2480 			'padding-right',
  2339 			'padding-bottom',
  2481 			'padding-bottom',
  2340 			'padding-left',
  2482 			'padding-left',
  2341 			'padding-top',
  2483 			'padding-top',
       
  2484 			'padding-block-start',
       
  2485 			'padding-block-end',
       
  2486 			'padding-inline-start',
       
  2487 			'padding-inline-end',
  2342 
  2488 
  2343 			'flex',
  2489 			'flex',
  2344 			'flex-basis',
  2490 			'flex-basis',
  2345 			'flex-direction',
  2491 			'flex-direction',
  2346 			'flex-flow',
  2492 			'flex-flow',
  2347 			'flex-grow',
  2493 			'flex-grow',
  2348 			'flex-shrink',
  2494 			'flex-shrink',
       
  2495 			'flex-wrap',
       
  2496 
       
  2497 			'gap',
       
  2498 			'column-gap',
       
  2499 			'row-gap',
  2349 
  2500 
  2350 			'grid-template-columns',
  2501 			'grid-template-columns',
  2351 			'grid-auto-columns',
  2502 			'grid-auto-columns',
  2352 			'grid-column-start',
  2503 			'grid-column-start',
  2353 			'grid-column-end',
  2504 			'grid-column-end',
       
  2505 			'grid-column',
  2354 			'grid-column-gap',
  2506 			'grid-column-gap',
  2355 			'grid-template-rows',
  2507 			'grid-template-rows',
  2356 			'grid-auto-rows',
  2508 			'grid-auto-rows',
  2357 			'grid-row-start',
  2509 			'grid-row-start',
  2358 			'grid-row-end',
  2510 			'grid-row-end',
       
  2511 			'grid-row',
  2359 			'grid-row-gap',
  2512 			'grid-row-gap',
  2360 			'grid-gap',
  2513 			'grid-gap',
  2361 
  2514 
  2362 			'justify-content',
  2515 			'justify-content',
  2363 			'justify-items',
  2516 			'justify-items',
  2369 			'clear',
  2522 			'clear',
  2370 			'cursor',
  2523 			'cursor',
  2371 			'direction',
  2524 			'direction',
  2372 			'float',
  2525 			'float',
  2373 			'list-style-type',
  2526 			'list-style-type',
       
  2527 			'object-fit',
  2374 			'object-position',
  2528 			'object-position',
  2375 			'overflow',
  2529 			'overflow',
  2376 			'vertical-align',
  2530 			'vertical-align',
       
  2531 			'writing-mode',
       
  2532 
       
  2533 			'position',
       
  2534 			'top',
       
  2535 			'right',
       
  2536 			'bottom',
       
  2537 			'left',
       
  2538 			'z-index',
       
  2539 			'box-shadow',
       
  2540 			'aspect-ratio',
       
  2541 			'container-type',
       
  2542 
       
  2543 			// Custom CSS properties.
       
  2544 			'--*',
  2377 		)
  2545 		)
  2378 	);
  2546 	);
  2379 
  2547 
  2380 	/*
  2548 	/*
  2381 	 * CSS attributes that accept URL data types.
  2549 	 * CSS attributes that accept URL data types.
  2388 	$css_url_data_types = array(
  2556 	$css_url_data_types = array(
  2389 		'background',
  2557 		'background',
  2390 		'background-image',
  2558 		'background-image',
  2391 
  2559 
  2392 		'cursor',
  2560 		'cursor',
       
  2561 		'filter',
  2393 
  2562 
  2394 		'list-style',
  2563 		'list-style',
  2395 		'list-style-image',
  2564 		'list-style-image',
  2396 	);
  2565 	);
  2397 
  2566 
  2417 		$css_item        = trim( $css_item );
  2586 		$css_item        = trim( $css_item );
  2418 		$css_test_string = $css_item;
  2587 		$css_test_string = $css_item;
  2419 		$found           = false;
  2588 		$found           = false;
  2420 		$url_attr        = false;
  2589 		$url_attr        = false;
  2421 		$gradient_attr   = false;
  2590 		$gradient_attr   = false;
  2422 
  2591 		$is_custom_var   = false;
  2423 		if ( strpos( $css_item, ':' ) === false ) {
  2592 
       
  2593 		if ( ! str_contains( $css_item, ':' ) ) {
  2424 			$found = true;
  2594 			$found = true;
  2425 		} else {
  2595 		} else {
  2426 			$parts        = explode( ':', $css_item, 2 );
  2596 			$parts        = explode( ':', $css_item, 2 );
  2427 			$css_selector = trim( $parts[0] );
  2597 			$css_selector = trim( $parts[0] );
  2428 
  2598 
       
  2599 			// Allow assigning values to CSS variables.
       
  2600 			if ( in_array( '--*', $allowed_attr, true ) && preg_match( '/^--[a-zA-Z0-9-_]+$/', $css_selector ) ) {
       
  2601 				$allowed_attr[] = $css_selector;
       
  2602 				$is_custom_var  = true;
       
  2603 			}
       
  2604 
  2429 			if ( in_array( $css_selector, $allowed_attr, true ) ) {
  2605 			if ( in_array( $css_selector, $allowed_attr, true ) ) {
  2430 				$found         = true;
  2606 				$found         = true;
  2431 				$url_attr      = in_array( $css_selector, $css_url_data_types, true );
  2607 				$url_attr      = in_array( $css_selector, $css_url_data_types, true );
  2432 				$gradient_attr = in_array( $css_selector, $css_gradient_data_types, true );
  2608 				$gradient_attr = in_array( $css_selector, $css_gradient_data_types, true );
       
  2609 			}
       
  2610 
       
  2611 			if ( $is_custom_var ) {
       
  2612 				$css_value     = trim( $parts[1] );
       
  2613 				$url_attr      = str_starts_with( $css_value, 'url(' );
       
  2614 				$gradient_attr = str_contains( $css_value, '-gradient(' );
  2433 			}
  2615 			}
  2434 		}
  2616 		}
  2435 
  2617 
  2436 		if ( $found && $url_attr ) {
  2618 		if ( $found && $url_attr ) {
  2437 			// Simplified: matches the sequence `url(*)`.
  2619 			// Simplified: matches the sequence `url(*)`.
  2465 				$css_test_string = str_replace( $css_value, '', $css_test_string );
  2647 				$css_test_string = str_replace( $css_value, '', $css_test_string );
  2466 			}
  2648 			}
  2467 		}
  2649 		}
  2468 
  2650 
  2469 		if ( $found ) {
  2651 		if ( $found ) {
  2470 			// Allow CSS calc().
  2652 			/*
  2471 			$css_test_string = preg_replace( '/calc\(((?:\([^()]*\)?|[^()])*)\)/', '', $css_test_string );
  2653 			 * Allow CSS functions like var(), calc(), etc. by removing them from the test string.
  2472 			// Allow CSS var().
  2654 			 * Nested functions and parentheses are also removed, so long as the parentheses are balanced.
  2473 			$css_test_string = preg_replace( '/\(?var\(--[a-zA-Z0-9_-]*\)/', '', $css_test_string );
  2655 			 */
  2474 
  2656 			$css_test_string = preg_replace(
  2475 			// Check for any CSS containing \ ( & } = or comments,
  2657 				'/\b(?:var|calc|min|max|minmax|clamp|repeat)(\((?:[^()]|(?1))*\))/',
  2476 			// except for url(), calc(), or var() usage checked above.
  2658 				'',
       
  2659 				$css_test_string
       
  2660 			);
       
  2661 
       
  2662 			/*
       
  2663 			 * Disallow CSS containing \ ( & } = or comments, except for within url(), var(), calc(), etc.
       
  2664 			 * which were removed from the test string above.
       
  2665 			 */
  2477 			$allow_css = ! preg_match( '%[\\\(&=}]|/\*%', $css_test_string );
  2666 			$allow_css = ! preg_match( '%[\\\(&=}]|/\*%', $css_test_string );
  2478 
  2667 
  2479 			/**
  2668 			/**
  2480 			 * Filters the check for unsafe CSS in `safecss_filter_attr`.
  2669 			 * Filters the check for unsafe CSS in `safecss_filter_attr`.
  2481 			 *
  2670 			 *
  2508  * Helper function to add global attributes to a tag in the allowed HTML list.
  2697  * Helper function to add global attributes to a tag in the allowed HTML list.
  2509  *
  2698  *
  2510  * @since 3.5.0
  2699  * @since 3.5.0
  2511  * @since 5.0.0 Added support for `data-*` wildcard attributes.
  2700  * @since 5.0.0 Added support for `data-*` wildcard attributes.
  2512  * @since 6.0.0 Added `dir`, `lang`, and `xml:lang` to global attributes.
  2701  * @since 6.0.0 Added `dir`, `lang`, and `xml:lang` to global attributes.
       
  2702  * @since 6.3.0 Added `aria-controls`, `aria-current`, and `aria-expanded` attributes.
       
  2703  * @since 6.4.0 Added `aria-live` and `hidden` attributes.
  2513  *
  2704  *
  2514  * @access private
  2705  * @access private
  2515  * @ignore
  2706  * @ignore
  2516  *
  2707  *
  2517  * @param array $value An array of attributes.
  2708  * @param array $value An array of attributes.
  2518  * @return array The array of attributes with global attributes added.
  2709  * @return array The array of attributes with global attributes added.
  2519  */
  2710  */
  2520 function _wp_add_global_attributes( $value ) {
  2711 function _wp_add_global_attributes( $value ) {
  2521 	$global_attributes = array(
  2712 	$global_attributes = array(
       
  2713 		'aria-controls'    => true,
       
  2714 		'aria-current'     => true,
  2522 		'aria-describedby' => true,
  2715 		'aria-describedby' => true,
  2523 		'aria-details'     => true,
  2716 		'aria-details'     => true,
       
  2717 		'aria-expanded'    => true,
       
  2718 		'aria-hidden'      => true,
  2524 		'aria-label'       => true,
  2719 		'aria-label'       => true,
  2525 		'aria-labelledby'  => true,
  2720 		'aria-labelledby'  => true,
  2526 		'aria-hidden'      => true,
  2721 		'aria-live'        => true,
  2527 		'class'            => true,
  2722 		'class'            => true,
  2528 		'data-*'           => true,
  2723 		'data-*'           => true,
  2529 		'dir'              => true,
  2724 		'dir'              => true,
       
  2725 		'hidden'           => true,
  2530 		'id'               => true,
  2726 		'id'               => true,
  2531 		'lang'             => true,
  2727 		'lang'             => true,
  2532 		'style'            => true,
  2728 		'style'            => true,
  2533 		'title'            => true,
  2729 		'title'            => true,
  2534 		'role'             => true,
  2730 		'role'             => true,