wp/wp-includes/kses.php
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
    45 	define( 'CUSTOM_TAGS', false );
    45 	define( 'CUSTOM_TAGS', false );
    46 }
    46 }
    47 
    47 
    48 // Ensure that these variables are added to the global namespace
    48 // Ensure that these variables are added to the global namespace
    49 // (e.g. if using namespaces / autoload in the current PHP environment).
    49 // (e.g. if using namespaces / autoload in the current PHP environment).
    50 global $allowedposttags, $allowedtags, $allowedentitynames;
    50 global $allowedposttags, $allowedtags, $allowedentitynames, $allowedxmlentitynames;
    51 
    51 
    52 if ( ! CUSTOM_TAGS ) {
    52 if ( ! CUSTOM_TAGS ) {
    53 	/**
    53 	/**
    54 	 * KSES global for default allowable HTML tags.
    54 	 * KSES global for default allowable HTML tags.
    55 	 *
    55 	 *
   228 			'alt'      => true,
   228 			'alt'      => true,
   229 			'align'    => true,
   229 			'align'    => true,
   230 			'border'   => true,
   230 			'border'   => true,
   231 			'height'   => true,
   231 			'height'   => true,
   232 			'hspace'   => true,
   232 			'hspace'   => true,
       
   233 			'loading'  => true,
   233 			'longdesc' => true,
   234 			'longdesc' => true,
   234 			'vspace'   => true,
   235 			'vspace'   => true,
   235 			'src'      => true,
   236 			'src'      => true,
   236 			'usemap'   => true,
   237 			'usemap'   => true,
   237 			'width'    => true,
   238 			'width'    => true,
   395 			'type'     => true,
   396 			'type'     => true,
   396 			'reversed' => true,
   397 			'reversed' => true,
   397 		),
   398 		),
   398 		'var'        => array(),
   399 		'var'        => array(),
   399 		'video'      => array(
   400 		'video'      => array(
   400 			'autoplay' => true,
   401 			'autoplay'    => true,
   401 			'controls' => true,
   402 			'controls'    => true,
   402 			'height'   => true,
   403 			'height'      => true,
   403 			'loop'     => true,
   404 			'loop'        => true,
   404 			'muted'    => true,
   405 			'muted'       => true,
   405 			'poster'   => true,
   406 			'playsinline' => true,
   406 			'preload'  => true,
   407 			'poster'      => true,
   407 			'src'      => true,
   408 			'preload'     => true,
   408 			'width'    => true,
   409 			'src'         => true,
       
   410 			'width'       => true,
   409 		),
   411 		),
   410 	);
   412 	);
   411 
   413 
   412 	/**
   414 	/**
   413 	 * @var array[] $allowedtags Array of KSES allowed HTML elements.
   415 	 * @var array[] $allowedtags Array of KSES allowed HTML elements.
   701 		'frac12',
   703 		'frac12',
   702 		'frac34',
   704 		'frac34',
   703 		'there4',
   705 		'there4',
   704 	);
   706 	);
   705 
   707 
       
   708 	/**
       
   709 	 * @var string[] $allowedxmlentitynames Array of KSES allowed XML entitity names.
       
   710 	 * @since 5.5.0
       
   711 	 */
       
   712 	$allowedxmlnamedentities = array(
       
   713 		'amp',
       
   714 		'lt',
       
   715 		'gt',
       
   716 		'apos',
       
   717 		'quot',
       
   718 	);
       
   719 
   706 	$allowedposttags = array_map( '_wp_add_global_attributes', $allowedposttags );
   720 	$allowedposttags = array_map( '_wp_add_global_attributes', $allowedposttags );
   707 } else {
   721 } else {
   708 	$allowedtags     = wp_kses_array_lc( $allowedtags );
   722 	$allowedtags     = wp_kses_array_lc( $allowedtags );
   709 	$allowedposttags = wp_kses_array_lc( $allowedposttags );
   723 	$allowedposttags = wp_kses_array_lc( $allowedposttags );
   710 }
   724 }
   721  * @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.
   722  *
   736  *
   723  * @since 1.0.0
   737  * @since 1.0.0
   724  *
   738  *
   725  * @param string         $string            Text content to filter.
   739  * @param string         $string            Text content to filter.
   726  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes, or a
   740  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
   727  *                                          context name such as 'post'.
   741  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
       
   742  *                                          for the list of accepted context names.
   728  * @param string[]       $allowed_protocols Array of allowed URL protocols.
   743  * @param string[]       $allowed_protocols Array of allowed URL protocols.
   729  * @return string Filtered content containing only the allowed HTML.
   744  * @return string Filtered content containing only the allowed HTML.
   730  */
   745  */
   731 function wp_kses( $string, $allowed_html, $allowed_protocols = array() ) {
   746 function wp_kses( $string, $allowed_html, $allowed_protocols = array() ) {
   732 	if ( empty( $allowed_protocols ) ) {
   747 	if ( empty( $allowed_protocols ) ) {
   733 		$allowed_protocols = wp_allowed_protocols();
   748 		$allowed_protocols = wp_allowed_protocols();
   734 	}
   749 	}
       
   750 
   735 	$string = wp_kses_no_null( $string, array( 'slash_zero' => 'keep' ) );
   751 	$string = wp_kses_no_null( $string, array( 'slash_zero' => 'keep' ) );
   736 	$string = wp_kses_normalize_entities( $string );
   752 	$string = wp_kses_normalize_entities( $string );
   737 	$string = wp_kses_hook( $string, $allowed_html, $allowed_protocols );
   753 	$string = wp_kses_hook( $string, $allowed_html, $allowed_protocols );
       
   754 
   738 	return wp_kses_split( $string, $allowed_html, $allowed_protocols );
   755 	return wp_kses_split( $string, $allowed_html, $allowed_protocols );
   739 }
   756 }
   740 
   757 
   741 /**
   758 /**
   742  * Filters one HTML attribute and ensures its value is allowed.
   759  * Filters one HTML attribute and ensures its value is allowed.
   773 	if ( count( $split ) == 2 ) {
   790 	if ( count( $split ) == 2 ) {
   774 		$value = $split[1];
   791 		$value = $split[1];
   775 
   792 
   776 		// Remove quotes surrounding $value.
   793 		// Remove quotes surrounding $value.
   777 		// Also guarantee correct quoting in $string for this one attribute.
   794 		// Also guarantee correct quoting in $string for this one attribute.
   778 		if ( '' == $value ) {
   795 		if ( '' === $value ) {
   779 			$quote = '';
   796 			$quote = '';
   780 		} else {
   797 		} else {
   781 			$quote = $value[0];
   798 			$quote = $value[0];
   782 		}
   799 		}
   783 		if ( '"' == $quote || "'" == $quote ) {
   800 		if ( '"' === $quote || "'" === $quote ) {
   784 			if ( substr( $value, -1 ) != $quote ) {
   801 			if ( substr( $value, -1 ) != $quote ) {
   785 				return '';
   802 				return '';
   786 			}
   803 			}
   787 			$value = substr( $value, 1, -1 );
   804 			$value = substr( $value, 1, -1 );
   788 		} else {
   805 		} else {
   791 
   808 
   792 		// Sanitize quotes, angle braces, and entities.
   809 		// Sanitize quotes, angle braces, and entities.
   793 		$value = esc_attr( $value );
   810 		$value = esc_attr( $value );
   794 
   811 
   795 		// Sanitize URI values.
   812 		// Sanitize URI values.
   796 		if ( in_array( strtolower( $name ), $uris ) ) {
   813 		if ( in_array( strtolower( $name ), $uris, true ) ) {
   797 			$value = wp_kses_bad_protocol( $value, $allowed_protocols );
   814 			$value = wp_kses_bad_protocol( $value, $allowed_protocols );
   798 		}
   815 		}
   799 
   816 
   800 		$string = "$name=$quote$value$quote";
   817 		$string = "$name=$quote$value$quote";
   801 		$vless  = 'n';
   818 		$vless  = 'n';
   894  * There is currently only one KSES WordPress hook, {@see 'pre_kses'}, and it is called here.
   911  * There is currently only one KSES WordPress hook, {@see 'pre_kses'}, and it is called here.
   895  * All parameters are passed to the hooks and expected to receive a string.
   912  * All parameters are passed to the hooks and expected to receive a string.
   896  *
   913  *
   897  * @since 1.0.0
   914  * @since 1.0.0
   898  *
   915  *
   899  * @param string          $string            Content to filter through KSES.
   916  * @param string         $string            Content to filter through KSES.
   900  * @param array[]|string  $allowed_html      List of allowed HTML elements.
   917  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
   901  * @param string[]        $allowed_protocols Array of allowed URL protocols.
   918  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
       
   919  *                                          for the list of accepted context names.
       
   920  * @param string[]       $allowed_protocols Array of allowed URL protocols.
   902  * @return string Filtered content through {@see 'pre_kses'} hook.
   921  * @return string Filtered content through {@see 'pre_kses'} hook.
   903  */
   922  */
   904 function wp_kses_hook( $string, $allowed_html, $allowed_protocols ) {
   923 function wp_kses_hook( $string, $allowed_html, $allowed_protocols ) {
   905 	/**
   924 	/**
   906 	 * Filters content to be run through kses.
   925 	 * Filters content to be run through KSES.
   907 	 *
   926 	 *
   908 	 * @since 2.3.0
   927 	 * @since 2.3.0
   909 	 *
   928 	 *
   910 	 * @param string          $string            Content to run through KSES.
   929 	 * @param string         $string            Content to filter through KSES.
   911 	 * @param array[]|string  $allowed_html      Allowed HTML elements.
   930 	 * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
   912 	 * @param string[]        $allowed_protocols Array of allowed URL protocols.
   931 	 *                                          or a context name such as 'post'. See wp_kses_allowed_html()
       
   932 	 *                                          for the list of accepted context names.
       
   933 	 * @param string[]       $allowed_protocols Array of allowed URL protocols.
   913 	 */
   934 	 */
   914 	return apply_filters( 'pre_kses', $string, $allowed_html, $allowed_protocols );
   935 	return apply_filters( 'pre_kses', $string, $allowed_html, $allowed_protocols );
   915 }
   936 }
   916 
   937 
   917 /**
   938 /**
   930  *
   951  *
   931  * It also matches stray `>` characters.
   952  * It also matches stray `>` characters.
   932  *
   953  *
   933  * @since 1.0.0
   954  * @since 1.0.0
   934  *
   955  *
   935  * @global array $pass_allowed_html
   956  * @global array[]|string $pass_allowed_html      An array of allowed HTML elements and attributes,
   936  * @global array $pass_allowed_protocols
   957  *                                                or a context name such as 'post'.
   937  *
   958  * @global string[]       $pass_allowed_protocols Array of allowed URL protocols.
   938  * @param string   $string            Content to filter.
   959  *
   939  * @param array    $allowed_html      Allowed HTML elements.
   960  * @param string         $string            Content to filter.
   940  * @param string[] $allowed_protocols Array of allowed URL protocols.
   961  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
       
   962  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
       
   963  *                                          for the list of accepted context names.
       
   964  * @param string[]       $allowed_protocols Array of allowed URL protocols.
   941  * @return string Content with fixed HTML tags
   965  * @return string Content with fixed HTML tags
   942  */
   966  */
   943 function wp_kses_split( $string, $allowed_html, $allowed_protocols ) {
   967 function wp_kses_split( $string, $allowed_html, $allowed_protocols ) {
   944 	global $pass_allowed_html, $pass_allowed_protocols;
   968 	global $pass_allowed_html, $pass_allowed_protocols;
       
   969 
   945 	$pass_allowed_html      = $allowed_html;
   970 	$pass_allowed_html      = $allowed_html;
   946 	$pass_allowed_protocols = $allowed_protocols;
   971 	$pass_allowed_protocols = $allowed_protocols;
       
   972 
   947 	return preg_replace_callback( '%(<!--.*?(-->|$))|(<[^>]*(>|$)|>)%', '_wp_kses_split_callback', $string );
   973 	return preg_replace_callback( '%(<!--.*?(-->|$))|(<[^>]*(>|$)|>)%', '_wp_kses_split_callback', $string );
   948 }
   974 }
   949 
   975 
   950 /**
   976 /**
   951  * Helper function listing HTML attributes containing a URL.
   977  * Returns an array of HTML attribute names whose value contains a URL.
   952  *
   978  *
   953  * This function returns a list of all HTML attributes that must contain
   979  * This function returns a list of all HTML attributes that must contain
   954  * a URL according to the HTML specification.
   980  * a URL according to the HTML specification.
   955  *
   981  *
   956  * This list includes URI attributes both allowed and disallowed by KSES.
   982  * This list includes URI attributes both allowed and disallowed by KSES.
   957  *
   983  *
   958  * @link https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
   984  * @link https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
   959  *
   985  *
   960  * @since 5.0.1
   986  * @since 5.0.1
   961  *
   987  *
   962  * @return array HTML attributes that must include a URL.
   988  * @return string[] HTML attribute names whose value contains a URL.
   963  */
   989  */
   964 function wp_kses_uri_attributes() {
   990 function wp_kses_uri_attributes() {
   965 	$uri_attributes = array(
   991 	$uri_attributes = array(
   966 		'action',
   992 		'action',
   967 		'archive',
   993 		'archive',
   988 	 * Use this filter to add any `data-` attributes that are required to be
  1014 	 * Use this filter to add any `data-` attributes that are required to be
   989 	 * validated as a URL.
  1015 	 * validated as a URL.
   990 	 *
  1016 	 *
   991 	 * @since 5.0.1
  1017 	 * @since 5.0.1
   992 	 *
  1018 	 *
   993 	 * @param array $uri_attributes HTML attributes requiring validation as a URL.
  1019 	 * @param string[] $uri_attributes HTML attribute names whose value contains a URL.
   994 	 */
  1020 	 */
   995 	$uri_attributes = apply_filters( 'wp_kses_uri_attributes', $uri_attributes );
  1021 	$uri_attributes = apply_filters( 'wp_kses_uri_attributes', $uri_attributes );
   996 
  1022 
   997 	return $uri_attributes;
  1023 	return $uri_attributes;
   998 }
  1024 }
  1002  *
  1028  *
  1003  * @since 3.1.0
  1029  * @since 3.1.0
  1004  * @access private
  1030  * @access private
  1005  * @ignore
  1031  * @ignore
  1006  *
  1032  *
  1007  * @global array $pass_allowed_html
  1033  * @global array[]|string $pass_allowed_html      An array of allowed HTML elements and attributes,
  1008  * @global array $pass_allowed_protocols
  1034  *                                                or a context name such as 'post'.
  1009  *
  1035  * @global string[]       $pass_allowed_protocols Array of allowed URL protocols.
       
  1036  *
       
  1037  * @param array $matches preg_replace regexp matches
  1010  * @return string
  1038  * @return string
  1011  */
  1039  */
  1012 function _wp_kses_split_callback( $match ) {
  1040 function _wp_kses_split_callback( $match ) {
  1013 	global $pass_allowed_html, $pass_allowed_protocols;
  1041 	global $pass_allowed_html, $pass_allowed_protocols;
       
  1042 
  1014 	return wp_kses_split2( $match[0], $pass_allowed_html, $pass_allowed_protocols );
  1043 	return wp_kses_split2( $match[0], $pass_allowed_html, $pass_allowed_protocols );
  1015 }
  1044 }
  1016 
  1045 
  1017 /**
  1046 /**
  1018  * Callback for `wp_kses_split()` for fixing malformed HTML tags.
  1047  * Callback for `wp_kses_split()` for fixing malformed HTML tags.
  1028  *
  1057  *
  1029  * @access private
  1058  * @access private
  1030  * @ignore
  1059  * @ignore
  1031  * @since 1.0.0
  1060  * @since 1.0.0
  1032  *
  1061  *
  1033  * @param string   $string            Content to filter.
  1062  * @param string         $string            Content to filter.
  1034  * @param array    $allowed_html      Allowed HTML elements.
  1063  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
  1035  * @param string[] $allowed_protocols Array of allowed URL protocols.
  1064  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
       
  1065  *                                          for the list of accepted context names.
       
  1066  * @param string[]       $allowed_protocols Array of allowed URL protocols.
  1036  * @return string Fixed HTML element
  1067  * @return string Fixed HTML element
  1037  */
  1068  */
  1038 function wp_kses_split2( $string, $allowed_html, $allowed_protocols ) {
  1069 function wp_kses_split2( $string, $allowed_html, $allowed_protocols ) {
  1039 	$string = wp_kses_stripslashes( $string );
  1070 	$string = wp_kses_stripslashes( $string );
  1040 
  1071 
  1041 	// It matched a ">" character.
  1072 	// It matched a ">" character.
  1042 	if ( substr( $string, 0, 1 ) != '<' ) {
  1073 	if ( '<' !== substr( $string, 0, 1 ) ) {
  1043 		return '&gt;';
  1074 		return '&gt;';
  1044 	}
  1075 	}
  1045 
  1076 
  1046 	// Allow HTML comments.
  1077 	// Allow HTML comments.
  1047 	if ( '<!--' == substr( $string, 0, 4 ) ) {
  1078 	if ( '<!--' === substr( $string, 0, 4 ) ) {
  1048 		$string = str_replace( array( '<!--', '-->' ), '', $string );
  1079 		$string = str_replace( array( '<!--', '-->' ), '', $string );
  1049 		while ( $string != ( $newstring = wp_kses( $string, $allowed_html, $allowed_protocols ) ) ) {
  1080 		while ( ( $newstring = wp_kses( $string, $allowed_html, $allowed_protocols ) ) != $string ) {
  1050 			$string = $newstring;
  1081 			$string = $newstring;
  1051 		}
  1082 		}
  1052 		if ( $string == '' ) {
  1083 		if ( '' === $string ) {
  1053 			return '';
  1084 			return '';
  1054 		}
  1085 		}
  1055 		// prevent multiple dashes in comments
  1086 		// Prevent multiple dashes in comments.
  1056 		$string = preg_replace( '/--+/', '-', $string );
  1087 		$string = preg_replace( '/--+/', '-', $string );
  1057 		// prevent three dashes closing a comment
  1088 		// Prevent three dashes closing a comment.
  1058 		$string = preg_replace( '/-$/', '', $string );
  1089 		$string = preg_replace( '/-$/', '', $string );
  1059 		return "<!--{$string}-->";
  1090 		return "<!--{$string}-->";
  1060 	}
  1091 	}
  1061 
  1092 
  1062 	// It's seriously malformed.
  1093 	// It's seriously malformed.
  1076 	if ( ! isset( $allowed_html[ strtolower( $elem ) ] ) ) {
  1107 	if ( ! isset( $allowed_html[ strtolower( $elem ) ] ) ) {
  1077 		return '';
  1108 		return '';
  1078 	}
  1109 	}
  1079 
  1110 
  1080 	// No attributes are allowed for closing elements.
  1111 	// No attributes are allowed for closing elements.
  1081 	if ( $slash != '' ) {
  1112 	if ( '' !== $slash ) {
  1082 		return "</$elem>";
  1113 		return "</$elem>";
  1083 	}
  1114 	}
  1084 
  1115 
  1085 	return wp_kses_attr( $elem, $attrlist, $allowed_html, $allowed_protocols );
  1116 	return wp_kses_attr( $elem, $attrlist, $allowed_html, $allowed_protocols );
  1086 }
  1117 }
  1094  * is to check if the tag has a closing XHTML slash, and if it does, it puts one
  1125  * is to check if the tag has a closing XHTML slash, and if it does, it puts one
  1095  * in the returned code as well.
  1126  * in the returned code as well.
  1096  *
  1127  *
  1097  * @since 1.0.0
  1128  * @since 1.0.0
  1098  *
  1129  *
  1099  * @param string   $element           HTML element/tag.
  1130  * @param string         $element           HTML element/tag.
  1100  * @param string   $attr              HTML attributes from HTML element to closing HTML element tag.
  1131  * @param string         $attr              HTML attributes from HTML element to closing HTML element tag.
  1101  * @param array    $allowed_html      Allowed HTML elements.
  1132  * @param array[]|string $allowed_html      An array of allowed HTML elements and attributes,
  1102  * @param string[] $allowed_protocols Array of allowed URL protocols.
  1133  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
       
  1134  *                                          for the list of accepted context names.
       
  1135  * @param string[]       $allowed_protocols Array of allowed URL protocols.
  1103  * @return string Sanitized HTML element.
  1136  * @return string Sanitized HTML element.
  1104  */
  1137  */
  1105 function wp_kses_attr( $element, $attr, $allowed_html, $allowed_protocols ) {
  1138 function wp_kses_attr( $element, $attr, $allowed_html, $allowed_protocols ) {
  1106 	if ( ! is_array( $allowed_html ) ) {
  1139 	if ( ! is_array( $allowed_html ) ) {
  1107 		$allowed_html = wp_kses_allowed_html( $allowed_html );
  1140 		$allowed_html = wp_kses_allowed_html( $allowed_html );
  1117 	$element_low = strtolower( $element );
  1150 	$element_low = strtolower( $element );
  1118 	if ( empty( $allowed_html[ $element_low ] ) || true === $allowed_html[ $element_low ] ) {
  1151 	if ( empty( $allowed_html[ $element_low ] ) || true === $allowed_html[ $element_low ] ) {
  1119 		return "<$element$xhtml_slash>";
  1152 		return "<$element$xhtml_slash>";
  1120 	}
  1153 	}
  1121 
  1154 
  1122 	// Split it
  1155 	// Split it.
  1123 	$attrarr = wp_kses_hair( $attr, $allowed_protocols );
  1156 	$attrarr = wp_kses_hair( $attr, $allowed_protocols );
  1124 
  1157 
  1125 	// Go through $attrarr, and save the allowed attributes for this element
  1158 	// Go through $attrarr, and save the allowed attributes for this element
  1126 	// in $attr2
  1159 	// in $attr2.
  1127 	$attr2 = '';
  1160 	$attr2 = '';
  1128 	foreach ( $attrarr as $arreach ) {
  1161 	foreach ( $attrarr as $arreach ) {
  1129 		if ( wp_kses_attr_check( $arreach['name'], $arreach['value'], $arreach['whole'], $arreach['vless'], $element, $allowed_html ) ) {
  1162 		if ( wp_kses_attr_check( $arreach['name'], $arreach['value'], $arreach['whole'], $arreach['vless'], $element, $allowed_html ) ) {
  1130 			$attr2 .= ' ' . $arreach['whole'];
  1163 			$attr2 .= ' ' . $arreach['whole'];
  1131 		}
  1164 		}
  1132 	}
  1165 	}
  1133 
  1166 
  1134 	// Remove any "<" or ">" characters
  1167 	// Remove any "<" or ">" characters.
  1135 	$attr2 = preg_replace( '/[<>]/', '', $attr2 );
  1168 	$attr2 = preg_replace( '/[<>]/', '', $attr2 );
  1136 
  1169 
  1137 	return "<$element$attr2$xhtml_slash>";
  1170 	return "<$element$attr2$xhtml_slash>";
  1138 }
  1171 }
  1139 
  1172 
  1150  * @param string $element      The name of the element to which this attribute belongs.
  1183  * @param string $element      The name of the element to which this attribute belongs.
  1151  * @param array  $allowed_html The full list of allowed elements and attributes.
  1184  * @param array  $allowed_html The full list of allowed elements and attributes.
  1152  * @return bool Whether or not the attribute is allowed.
  1185  * @return bool Whether or not the attribute is allowed.
  1153  */
  1186  */
  1154 function wp_kses_attr_check( &$name, &$value, &$whole, $vless, $element, $allowed_html ) {
  1187 function wp_kses_attr_check( &$name, &$value, &$whole, $vless, $element, $allowed_html ) {
  1155 	$allowed_attr = $allowed_html[ strtolower( $element ) ];
  1188 	$name_low    = strtolower( $name );
  1156 
  1189 	$element_low = strtolower( $element );
  1157 	$name_low = strtolower( $name );
  1190 
  1158 	if ( ! isset( $allowed_attr[ $name_low ] ) || '' == $allowed_attr[ $name_low ] ) {
  1191 	if ( ! isset( $allowed_html[ $element_low ] ) ) {
       
  1192 		$name  = '';
       
  1193 		$value = '';
       
  1194 		$whole = '';
       
  1195 		return false;
       
  1196 	}
       
  1197 
       
  1198 	$allowed_attr = $allowed_html[ $element_low ];
       
  1199 
       
  1200 	if ( ! isset( $allowed_attr[ $name_low ] ) || '' === $allowed_attr[ $name_low ] ) {
  1159 		/*
  1201 		/*
  1160 		 * Allow `data-*` attributes.
  1202 		 * Allow `data-*` attributes.
  1161 		 *
  1203 		 *
  1162 		 * When specifying `$allowed_html`, the attribute name should be set as
  1204 		 * When specifying `$allowed_html`, the attribute name should be set as
  1163 		 * `data-*` (not to be mixed with the HTML 4.0 `data` attribute, see
  1205 		 * `data-*` (not to be mixed with the HTML 4.0 `data` attribute, see
  1171 			 * Add the whole attribute name to the allowed attributes and set any restrictions
  1213 			 * Add the whole attribute name to the allowed attributes and set any restrictions
  1172 			 * for the `data-*` attribute values for the current element.
  1214 			 * for the `data-*` attribute values for the current element.
  1173 			 */
  1215 			 */
  1174 			$allowed_attr[ $match[0] ] = $allowed_attr['data-*'];
  1216 			$allowed_attr[ $match[0] ] = $allowed_attr['data-*'];
  1175 		} else {
  1217 		} else {
  1176 			$name = $value = $whole = '';
  1218 			$name  = '';
       
  1219 			$value = '';
       
  1220 			$whole = '';
  1177 			return false;
  1221 			return false;
  1178 		}
  1222 		}
  1179 	}
  1223 	}
  1180 
  1224 
  1181 	if ( 'style' == $name_low ) {
  1225 	if ( 'style' === $name_low ) {
  1182 		$new_value = safecss_filter_attr( $value );
  1226 		$new_value = safecss_filter_attr( $value );
  1183 
  1227 
  1184 		if ( empty( $new_value ) ) {
  1228 		if ( empty( $new_value ) ) {
  1185 			$name = $value = $whole = '';
  1229 			$name  = '';
       
  1230 			$value = '';
       
  1231 			$whole = '';
  1186 			return false;
  1232 			return false;
  1187 		}
  1233 		}
  1188 
  1234 
  1189 		$whole = str_replace( $value, $new_value, $whole );
  1235 		$whole = str_replace( $value, $new_value, $whole );
  1190 		$value = $new_value;
  1236 		$value = $new_value;
  1191 	}
  1237 	}
  1192 
  1238 
  1193 	if ( is_array( $allowed_attr[ $name_low ] ) ) {
  1239 	if ( is_array( $allowed_attr[ $name_low ] ) ) {
  1194 		// there are some checks
  1240 		// There are some checks.
  1195 		foreach ( $allowed_attr[ $name_low ] as $currkey => $currval ) {
  1241 		foreach ( $allowed_attr[ $name_low ] as $currkey => $currval ) {
  1196 			if ( ! wp_kses_check_attr_val( $value, $vless, $currkey, $currval ) ) {
  1242 			if ( ! wp_kses_check_attr_val( $value, $vless, $currkey, $currval ) ) {
  1197 				$name = $value = $whole = '';
  1243 				$name  = '';
       
  1244 				$value = '';
       
  1245 				$whole = '';
  1198 				return false;
  1246 				return false;
  1199 			}
  1247 			}
  1200 		}
  1248 		}
  1201 	}
  1249 	}
  1202 
  1250 
  1224 	$attrarr  = array();
  1272 	$attrarr  = array();
  1225 	$mode     = 0;
  1273 	$mode     = 0;
  1226 	$attrname = '';
  1274 	$attrname = '';
  1227 	$uris     = wp_kses_uri_attributes();
  1275 	$uris     = wp_kses_uri_attributes();
  1228 
  1276 
  1229 	// Loop through the whole attribute list
  1277 	// Loop through the whole attribute list.
  1230 
  1278 
  1231 	while ( strlen( $attr ) != 0 ) {
  1279 	while ( strlen( $attr ) != 0 ) {
  1232 		$working = 0; // Was the last operation successful?
  1280 		$working = 0; // Was the last operation successful?
  1233 
  1281 
  1234 		switch ( $mode ) {
  1282 		switch ( $mode ) {
  1235 			case 0:
  1283 			case 0:
  1236 				if ( preg_match( '/^([-a-zA-Z:]+)/', $attr, $match ) ) {
  1284 				if ( preg_match( '/^([_a-zA-Z][-_a-zA-Z0-9:.]*)/', $attr, $match ) ) {
  1237 					$attrname = $match[1];
  1285 					$attrname = $match[1];
  1238 					$working  = $mode = 1;
  1286 					$working  = 1;
  1239 					$attr     = preg_replace( '/^[-a-zA-Z:]+/', '', $attr );
  1287 					$mode     = 1;
       
  1288 					$attr     = preg_replace( '/^[_a-zA-Z][-_a-zA-Z0-9:.]*/', '', $attr );
  1240 				}
  1289 				}
  1241 
  1290 
  1242 				break;
  1291 				break;
  1243 
  1292 
  1244 			case 1:
  1293 			case 1:
  1245 				if ( preg_match( '/^\s*=\s*/', $attr ) ) { // equals sign
  1294 				if ( preg_match( '/^\s*=\s*/', $attr ) ) { // Equals sign.
  1246 					$working = 1;
  1295 					$working = 1;
  1247 					$mode    = 2;
  1296 					$mode    = 2;
  1248 					$attr    = preg_replace( '/^\s*=\s*/', '', $attr );
  1297 					$attr    = preg_replace( '/^\s*=\s*/', '', $attr );
  1249 					break;
  1298 					break;
  1250 				}
  1299 				}
  1251 
  1300 
  1252 				if ( preg_match( '/^\s+/', $attr ) ) { // valueless
  1301 				if ( preg_match( '/^\s+/', $attr ) ) { // Valueless.
  1253 					$working = 1;
  1302 					$working = 1;
  1254 					$mode    = 0;
  1303 					$mode    = 0;
  1255 					if ( false === array_key_exists( $attrname, $attrarr ) ) {
  1304 					if ( false === array_key_exists( $attrname, $attrarr ) ) {
  1256 						$attrarr[ $attrname ] = array(
  1305 						$attrarr[ $attrname ] = array(
  1257 							'name'  => $attrname,
  1306 							'name'  => $attrname,
  1267 
  1316 
  1268 			case 2:
  1317 			case 2:
  1269 				if ( preg_match( '%^"([^"]*)"(\s+|/?$)%', $attr, $match ) ) {
  1318 				if ( preg_match( '%^"([^"]*)"(\s+|/?$)%', $attr, $match ) ) {
  1270 					// "value"
  1319 					// "value"
  1271 					$thisval = $match[1];
  1320 					$thisval = $match[1];
  1272 					if ( in_array( strtolower( $attrname ), $uris ) ) {
  1321 					if ( in_array( strtolower( $attrname ), $uris, true ) ) {
  1273 						$thisval = wp_kses_bad_protocol( $thisval, $allowed_protocols );
  1322 						$thisval = wp_kses_bad_protocol( $thisval, $allowed_protocols );
  1274 					}
  1323 					}
  1275 
  1324 
  1276 					if ( false === array_key_exists( $attrname, $attrarr ) ) {
  1325 					if ( false === array_key_exists( $attrname, $attrarr ) ) {
  1277 						$attrarr[ $attrname ] = array(
  1326 						$attrarr[ $attrname ] = array(
  1288 				}
  1337 				}
  1289 
  1338 
  1290 				if ( preg_match( "%^'([^']*)'(\s+|/?$)%", $attr, $match ) ) {
  1339 				if ( preg_match( "%^'([^']*)'(\s+|/?$)%", $attr, $match ) ) {
  1291 					// 'value'
  1340 					// 'value'
  1292 					$thisval = $match[1];
  1341 					$thisval = $match[1];
  1293 					if ( in_array( strtolower( $attrname ), $uris ) ) {
  1342 					if ( in_array( strtolower( $attrname ), $uris, true ) ) {
  1294 						$thisval = wp_kses_bad_protocol( $thisval, $allowed_protocols );
  1343 						$thisval = wp_kses_bad_protocol( $thisval, $allowed_protocols );
  1295 					}
  1344 					}
  1296 
  1345 
  1297 					if ( false === array_key_exists( $attrname, $attrarr ) ) {
  1346 					if ( false === array_key_exists( $attrname, $attrarr ) ) {
  1298 						$attrarr[ $attrname ] = array(
  1347 						$attrarr[ $attrname ] = array(
  1309 				}
  1358 				}
  1310 
  1359 
  1311 				if ( preg_match( "%^([^\s\"']+)(\s+|/?$)%", $attr, $match ) ) {
  1360 				if ( preg_match( "%^([^\s\"']+)(\s+|/?$)%", $attr, $match ) ) {
  1312 					// value
  1361 					// value
  1313 					$thisval = $match[1];
  1362 					$thisval = $match[1];
  1314 					if ( in_array( strtolower( $attrname ), $uris ) ) {
  1363 					if ( in_array( strtolower( $attrname ), $uris, true ) ) {
  1315 						$thisval = wp_kses_bad_protocol( $thisval, $allowed_protocols );
  1364 						$thisval = wp_kses_bad_protocol( $thisval, $allowed_protocols );
  1316 					}
  1365 					}
  1317 
  1366 
  1318 					if ( false === array_key_exists( $attrname, $attrarr ) ) {
  1367 					if ( false === array_key_exists( $attrname, $attrarr ) ) {
  1319 						$attrarr[ $attrname ] = array(
  1368 						$attrarr[ $attrname ] = array(
  1328 					$mode    = 0;
  1377 					$mode    = 0;
  1329 					$attr    = preg_replace( "%^[^\s\"']+(\s+|$)%", '', $attr );
  1378 					$attr    = preg_replace( "%^[^\s\"']+(\s+|$)%", '', $attr );
  1330 				}
  1379 				}
  1331 
  1380 
  1332 				break;
  1381 				break;
  1333 		} // switch
  1382 		} // End switch.
  1334 
  1383 
  1335 		if ( $working == 0 ) { // not well formed, remove and try again
  1384 		if ( 0 == $working ) { // Not well-formed, remove and try again.
  1336 			$attr = wp_kses_html_error( $attr );
  1385 			$attr = wp_kses_html_error( $attr );
  1337 			$mode = 0;
  1386 			$mode = 0;
  1338 		}
  1387 		}
  1339 	} // while
  1388 	} // End while.
  1340 
  1389 
  1341 	if ( $mode == 1 && false === array_key_exists( $attrname, $attrarr ) ) {
  1390 	if ( 1 == $mode && false === array_key_exists( $attrname, $attrarr ) ) {
  1342 		// special case, for when the attribute list ends with a valueless
  1391 		// Special case, for when the attribute list ends with a valueless
  1343 		// attribute like "selected"
  1392 		// attribute like "selected".
  1344 		$attrarr[ $attrname ] = array(
  1393 		$attrarr[ $attrname ] = array(
  1345 			'name'  => $attrname,
  1394 			'name'  => $attrname,
  1346 			'value' => '',
  1395 			'value' => '',
  1347 			'whole' => $attrname,
  1396 			'whole' => $attrname,
  1348 			'vless' => 'y',
  1397 			'vless' => 'y',
  1387 		$attr        = substr( $attr, 0, -strlen( $xhtml_slash ) );
  1436 		$attr        = substr( $attr, 0, -strlen( $xhtml_slash ) );
  1388 	} else {
  1437 	} else {
  1389 		$xhtml_slash = '';
  1438 		$xhtml_slash = '';
  1390 	}
  1439 	}
  1391 
  1440 
  1392 	// Split it
  1441 	// Split it.
  1393 	$attrarr = wp_kses_hair_parse( $attr );
  1442 	$attrarr = wp_kses_hair_parse( $attr );
  1394 	if ( false === $attrarr ) {
  1443 	if ( false === $attrarr ) {
  1395 		return false;
  1444 		return false;
  1396 	}
  1445 	}
  1397 
  1446 
  1420 		return array();
  1469 		return array();
  1421 	}
  1470 	}
  1422 
  1471 
  1423 	// phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation
  1472 	// phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation
  1424 	$regex =
  1473 	$regex =
  1425 	'(?:'
  1474 		'(?:'
  1426 	.     '[-a-zA-Z:]+'   // Attribute name.
  1475 		.     '[_a-zA-Z][-_a-zA-Z0-9:.]*' // Attribute name.
  1427 	. '|'
  1476 		. '|'
  1428 	.     '\[\[?[^\[\]]+\]\]?' // Shortcode in the name position implies unfiltered_html.
  1477 		.     '\[\[?[^\[\]]+\]\]?'        // Shortcode in the name position implies unfiltered_html.
  1429 	. ')'
  1478 		. ')'
  1430 	. '(?:'               // Attribute value.
  1479 		. '(?:'               // Attribute value.
  1431 	.     '\s*=\s*'       // All values begin with '='
  1480 		.     '\s*=\s*'       // All values begin with '='.
  1432 	.     '(?:'
  1481 		.     '(?:'
  1433 	.         '"[^"]*"'   // Double-quoted
  1482 		.         '"[^"]*"'   // Double-quoted.
  1434 	.     '|'
  1483 		.     '|'
  1435 	.         "'[^']*'"   // Single-quoted
  1484 		.         "'[^']*'"   // Single-quoted.
  1436 	.     '|'
  1485 		.     '|'
  1437 	.         '[^\s"\']+' // Non-quoted
  1486 		.         '[^\s"\']+' // Non-quoted.
  1438 	.         '(?:\s|$)'  // Must have a space
  1487 		.         '(?:\s|$)'  // Must have a space.
  1439 	.     ')'
  1488 		.     ')'
  1440 	. '|'
  1489 		. '|'
  1441 	.     '(?:\s|$)'      // If attribute has no value, space is required.
  1490 		.     '(?:\s|$)'      // If attribute has no value, space is required.
  1442 	. ')'
  1491 		. ')'
  1443 	. '\s*';              // Trailing space is optional except as mentioned above.
  1492 		. '\s*';              // Trailing space is optional except as mentioned above.
  1444 	// phpcs:enable
  1493 	// phpcs:enable
  1445 
  1494 
  1446 	// Although it is possible to reduce this procedure to a single regexp,
  1495 	// Although it is possible to reduce this procedure to a single regexp,
  1447 	// we must run that regexp twice to get exactly the expected result.
  1496 	// we must run that regexp twice to get exactly the expected result.
  1448 
  1497 
  1474 function wp_kses_check_attr_val( $value, $vless, $checkname, $checkvalue ) {
  1523 function wp_kses_check_attr_val( $value, $vless, $checkname, $checkvalue ) {
  1475 	$ok = true;
  1524 	$ok = true;
  1476 
  1525 
  1477 	switch ( strtolower( $checkname ) ) {
  1526 	switch ( strtolower( $checkname ) ) {
  1478 		case 'maxlen':
  1527 		case 'maxlen':
  1479 			// The maxlen check makes sure that the attribute value has a length not
  1528 			/*
  1480 			// greater than the given value. This can be used to avoid Buffer Overflows
  1529 			 * The maxlen check makes sure that the attribute value has a length not
  1481 			// in WWW clients and various Internet servers.
  1530 			 * greater than the given value. This can be used to avoid Buffer Overflows
       
  1531 			 * in WWW clients and various Internet servers.
       
  1532 			 */
  1482 
  1533 
  1483 			if ( strlen( $value ) > $checkvalue ) {
  1534 			if ( strlen( $value ) > $checkvalue ) {
  1484 				$ok = false;
  1535 				$ok = false;
  1485 			}
  1536 			}
  1486 			break;
  1537 			break;
  1487 
  1538 
  1488 		case 'minlen':
  1539 		case 'minlen':
  1489 			// The minlen check makes sure that the attribute value has a length not
  1540 			/*
  1490 			// smaller than the given value.
  1541 			 * The minlen check makes sure that the attribute value has a length not
       
  1542 			 * smaller than the given value.
       
  1543 			 */
  1491 
  1544 
  1492 			if ( strlen( $value ) < $checkvalue ) {
  1545 			if ( strlen( $value ) < $checkvalue ) {
  1493 				$ok = false;
  1546 				$ok = false;
  1494 			}
  1547 			}
  1495 			break;
  1548 			break;
  1496 
  1549 
  1497 		case 'maxval':
  1550 		case 'maxval':
  1498 			// The maxval check does two things: it checks that the attribute value is
  1551 			/*
  1499 			// an integer from 0 and up, without an excessive amount of zeroes or
  1552 			 * The maxval check does two things: it checks that the attribute value is
  1500 			// whitespace (to avoid Buffer Overflows). It also checks that the attribute
  1553 			 * an integer from 0 and up, without an excessive amount of zeroes or
  1501 			// value is not greater than the given value.
  1554 			 * whitespace (to avoid Buffer Overflows). It also checks that the attribute
  1502 			// This check can be used to avoid Denial of Service attacks.
  1555 			 * value is not greater than the given value.
       
  1556 			 * This check can be used to avoid Denial of Service attacks.
       
  1557 			 */
  1503 
  1558 
  1504 			if ( ! preg_match( '/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value ) ) {
  1559 			if ( ! preg_match( '/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value ) ) {
  1505 				$ok = false;
  1560 				$ok = false;
  1506 			}
  1561 			}
  1507 			if ( $value > $checkvalue ) {
  1562 			if ( $value > $checkvalue ) {
  1508 				$ok = false;
  1563 				$ok = false;
  1509 			}
  1564 			}
  1510 			break;
  1565 			break;
  1511 
  1566 
  1512 		case 'minval':
  1567 		case 'minval':
  1513 			// The minval check makes sure that the attribute value is a positive integer,
  1568 			/*
  1514 			// and that it is not smaller than the given value.
  1569 			 * The minval check makes sure that the attribute value is a positive integer,
       
  1570 			 * and that it is not smaller than the given value.
       
  1571 			 */
  1515 
  1572 
  1516 			if ( ! preg_match( '/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value ) ) {
  1573 			if ( ! preg_match( '/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value ) ) {
  1517 				$ok = false;
  1574 				$ok = false;
  1518 			}
  1575 			}
  1519 			if ( $value < $checkvalue ) {
  1576 			if ( $value < $checkvalue ) {
  1520 				$ok = false;
  1577 				$ok = false;
  1521 			}
  1578 			}
  1522 			break;
  1579 			break;
  1523 
  1580 
  1524 		case 'valueless':
  1581 		case 'valueless':
  1525 			// The valueless check makes sure if the attribute has a value
  1582 			/*
  1526 			// (like `<a href="blah">`) or not (`<option selected>`). If the given value
  1583 			 * The valueless check makes sure if the attribute has a value
  1527 			// is a "y" or a "Y", the attribute must not have a value.
  1584 			 * (like `<a href="blah">`) or not (`<option selected>`). If the given value
  1528 			// If the given value is an "n" or an "N", the attribute must have a value.
  1585 			 * is a "y" or a "Y", the attribute must not have a value.
       
  1586 			 * If the given value is an "n" or an "N", the attribute must have a value.
       
  1587 			 */
  1529 
  1588 
  1530 			if ( strtolower( $checkvalue ) != $vless ) {
  1589 			if ( strtolower( $checkvalue ) != $vless ) {
  1531 				$ok = false;
  1590 				$ok = false;
  1532 			}
  1591 			}
  1533 			break;
  1592 			break;
  1534 	} // switch
  1593 	} // End switch.
  1535 
  1594 
  1536 	return $ok;
  1595 	return $ok;
  1537 }
  1596 }
  1538 
  1597 
  1539 /**
  1598 /**
  1581 	if ( ! isset( $options['slash_zero'] ) ) {
  1640 	if ( ! isset( $options['slash_zero'] ) ) {
  1582 		$options = array( 'slash_zero' => 'remove' );
  1641 		$options = array( 'slash_zero' => 'remove' );
  1583 	}
  1642 	}
  1584 
  1643 
  1585 	$string = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $string );
  1644 	$string = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $string );
  1586 	if ( 'remove' == $options['slash_zero'] ) {
  1645 	if ( 'remove' === $options['slash_zero'] ) {
  1587 		$string = preg_replace( '/\\\\+0+/', '', $string );
  1646 		$string = preg_replace( '/\\\\+0+/', '', $string );
  1588 	}
  1647 	}
  1589 
  1648 
  1590 	return $string;
  1649 	return $string;
  1591 }
  1650 }
  1652  *
  1711  *
  1653  * @since 1.0.0
  1712  * @since 1.0.0
  1654  *
  1713  *
  1655  * @param string   $string            Content to check for bad protocols.
  1714  * @param string   $string            Content to check for bad protocols.
  1656  * @param string[] $allowed_protocols Array of allowed URL protocols.
  1715  * @param string[] $allowed_protocols Array of allowed URL protocols.
       
  1716  * @param int      $count             Depth of call recursion to this function.
  1657  * @return string Sanitized content.
  1717  * @return string Sanitized content.
  1658  */
  1718  */
  1659 function wp_kses_bad_protocol_once( $string, $allowed_protocols, $count = 1 ) {
  1719 function wp_kses_bad_protocol_once( $string, $allowed_protocols, $count = 1 ) {
  1660 	$string  = preg_replace( '/(&#0*58(?![;0-9])|&#x0*3a(?![;a-f0-9]))/i', '$1;', $string );
  1720 	$string  = preg_replace( '/(&#0*58(?![;0-9])|&#x0*3a(?![;a-f0-9]))/i', '$1;', $string );
  1661 	$string2 = preg_split( '/:|&#0*58;|&#x0*3a;/i', $string, 2 );
  1721 	$string2 = preg_split( '/:|&#0*58;|&#x0*3a;|&colon;/i', $string, 2 );
  1662 	if ( isset( $string2[1] ) && ! preg_match( '%/\?%', $string2[0] ) ) {
  1722 	if ( isset( $string2[1] ) && ! preg_match( '%/\?%', $string2[0] ) ) {
  1663 		$string   = trim( $string2[1] );
  1723 		$string   = trim( $string2[1] );
  1664 		$protocol = wp_kses_bad_protocol_once2( $string2[0], $allowed_protocols );
  1724 		$protocol = wp_kses_bad_protocol_once2( $string2[0], $allowed_protocols );
  1665 		if ( 'feed:' == $protocol ) {
  1725 		if ( 'feed:' === $protocol ) {
  1666 			if ( $count > 2 ) {
  1726 			if ( $count > 2 ) {
  1667 				return '';
  1727 				return '';
  1668 			}
  1728 			}
  1669 			$string = wp_kses_bad_protocol_once( $string, $allowed_protocols, ++$count );
  1729 			$string = wp_kses_bad_protocol_once( $string, $allowed_protocols, ++$count );
  1670 			if ( empty( $string ) ) {
  1730 			if ( empty( $string ) ) {
  1679 
  1739 
  1680 /**
  1740 /**
  1681  * Callback for `wp_kses_bad_protocol_once()` regular expression.
  1741  * Callback for `wp_kses_bad_protocol_once()` regular expression.
  1682  *
  1742  *
  1683  * This function processes URL protocols, checks to see if they're in the
  1743  * This function processes URL protocols, checks to see if they're in the
  1684  * whitelist or not, and returns different data depending on the answer.
  1744  * list of allowed protocols or not, and returns different data depending
       
  1745  * on the answer.
  1685  *
  1746  *
  1686  * @access private
  1747  * @access private
  1687  * @ignore
  1748  * @ignore
  1688  * @since 1.0.0
  1749  * @since 1.0.0
  1689  *
  1750  *
  1690  * @param string   $string            URI scheme to check against the whitelist.
  1751  * @param string   $string            URI scheme to check against the list of allowed protocols.
  1691  * @param string[] $allowed_protocols Array of allowed URL protocols.
  1752  * @param string[] $allowed_protocols Array of allowed URL protocols.
  1692  * @return string Sanitized content.
  1753  * @return string Sanitized content.
  1693  */
  1754  */
  1694 function wp_kses_bad_protocol_once2( $string, $allowed_protocols ) {
  1755 function wp_kses_bad_protocol_once2( $string, $allowed_protocols ) {
  1695 	$string2 = wp_kses_decode_entities( $string );
  1756 	$string2 = wp_kses_decode_entities( $string );
  1716  * Converts and fixes HTML entities.
  1777  * Converts and fixes HTML entities.
  1717  *
  1778  *
  1718  * This function normalizes HTML entities. It will convert `AT&T` to the correct
  1779  * This function normalizes HTML entities. It will convert `AT&T` to the correct
  1719  * `AT&amp;T`, `&#00058;` to `&#58;`, `&#XYZZY;` to `&amp;#XYZZY;` and so on.
  1780  * `AT&amp;T`, `&#00058;` to `&#58;`, `&#XYZZY;` to `&amp;#XYZZY;` and so on.
  1720  *
  1781  *
       
  1782  * When `$context` is set to 'xml', HTML entities are converted to their code points.  For
       
  1783  * example, `AT&T&hellip;&#XYZZY;` is converted to `AT&amp;T…&amp;#XYZZY;`.
       
  1784  *
  1721  * @since 1.0.0
  1785  * @since 1.0.0
  1722  *
  1786  * @since 5.5.0 Added `$context` parameter.
  1723  * @param string $string Content to normalize entities.
  1787  *
       
  1788  * @param string $string  Content to normalize entities.
       
  1789  * @param string $context Context for normalization. Can be either 'html' or 'xml'.
       
  1790  *                        Default 'html'.
  1724  * @return string Content with normalized entities.
  1791  * @return string Content with normalized entities.
  1725  */
  1792  */
  1726 function wp_kses_normalize_entities( $string ) {
  1793 function wp_kses_normalize_entities( $string, $context = 'html' ) {
  1727 	// Disarm all entities by converting & to &amp;
  1794 	// Disarm all entities by converting & to &amp;
  1728 	$string = str_replace( '&', '&amp;', $string );
  1795 	$string = str_replace( '&', '&amp;', $string );
  1729 
  1796 
  1730 	// Change back the allowed entities in our entity whitelist
  1797 	// Change back the allowed entities in our list of allowed entities.
  1731 	$string = preg_replace_callback( '/&amp;([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_named_entities', $string );
  1798 	if ( 'xml' === $context ) {
       
  1799 		$string = preg_replace_callback( '/&amp;([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_xml_named_entities', $string );
       
  1800 	} else {
       
  1801 		$string = preg_replace_callback( '/&amp;([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_named_entities', $string );
       
  1802 	}
  1732 	$string = preg_replace_callback( '/&amp;#(0*[0-9]{1,7});/', 'wp_kses_normalize_entities2', $string );
  1803 	$string = preg_replace_callback( '/&amp;#(0*[0-9]{1,7});/', 'wp_kses_normalize_entities2', $string );
  1733 	$string = preg_replace_callback( '/&amp;#[Xx](0*[0-9A-Fa-f]{1,6});/', 'wp_kses_normalize_entities3', $string );
  1804 	$string = preg_replace_callback( '/&amp;#[Xx](0*[0-9A-Fa-f]{1,6});/', 'wp_kses_normalize_entities3', $string );
  1734 
  1805 
  1735 	return $string;
  1806 	return $string;
  1736 }
  1807 }
  1754 	if ( empty( $matches[1] ) ) {
  1825 	if ( empty( $matches[1] ) ) {
  1755 		return '';
  1826 		return '';
  1756 	}
  1827 	}
  1757 
  1828 
  1758 	$i = $matches[1];
  1829 	$i = $matches[1];
  1759 	return ( ! in_array( $i, $allowedentitynames ) ) ? "&amp;$i;" : "&$i;";
  1830 	return ( ! in_array( $i, $allowedentitynames, true ) ) ? "&amp;$i;" : "&$i;";
       
  1831 }
       
  1832 
       
  1833 /**
       
  1834  * Callback for `wp_kses_normalize_entities()` regular expression.
       
  1835  *
       
  1836  * This function only accepts valid named entity references, which are finite,
       
  1837  * case-sensitive, and highly scrutinized by XML validators.  HTML named entity
       
  1838  * references are converted to their code points.
       
  1839  *
       
  1840  * @since 5.5.0
       
  1841  *
       
  1842  * @global array $allowedentitynames
       
  1843  * @global array $allowedxmlnamedentities
       
  1844  *
       
  1845  * @param array $matches preg_replace_callback() matches array.
       
  1846  * @return string Correctly encoded entity.
       
  1847  */
       
  1848 function wp_kses_xml_named_entities( $matches ) {
       
  1849 	global $allowedentitynames, $allowedxmlnamedentities;
       
  1850 
       
  1851 	if ( empty( $matches[1] ) ) {
       
  1852 		return '';
       
  1853 	}
       
  1854 
       
  1855 	$i = $matches[1];
       
  1856 
       
  1857 	if ( in_array( $i, $allowedxmlnamedentities, true ) ) {
       
  1858 		return "&$i;";
       
  1859 	} elseif ( in_array( $i, $allowedentitynames, true ) ) {
       
  1860 		return html_entity_decode( "&$i;", ENT_HTML5 );
       
  1861 	}
       
  1862 
       
  1863 	return "&amp;$i;";
  1760 }
  1864 }
  1761 
  1865 
  1762 /**
  1866 /**
  1763  * Callback for `wp_kses_normalize_entities()` regular expression.
  1867  * Callback for `wp_kses_normalize_entities()` regular expression.
  1764  *
  1868  *
  1817  *
  1921  *
  1818  * @param int $i Unicode codepoint.
  1922  * @param int $i Unicode codepoint.
  1819  * @return bool Whether or not the codepoint is a valid Unicode codepoint.
  1923  * @return bool Whether or not the codepoint is a valid Unicode codepoint.
  1820  */
  1924  */
  1821 function valid_unicode( $i ) {
  1925 function valid_unicode( $i ) {
  1822 	return ( $i == 0x9 || $i == 0xa || $i == 0xd ||
  1926 	return ( 0x9 == $i || 0xa == $i || 0xd == $i ||
  1823 			( $i >= 0x20 && $i <= 0xd7ff ) ||
  1927 			( 0x20 <= $i && $i <= 0xd7ff ) ||
  1824 			( $i >= 0xe000 && $i <= 0xfffd ) ||
  1928 			( 0xe000 <= $i && $i <= 0xfffd ) ||
  1825 			( $i >= 0x10000 && $i <= 0x10ffff ) );
  1929 			( 0x10000 <= $i && $i <= 0x10ffff ) );
  1826 }
  1930 }
  1827 
  1931 
  1828 /**
  1932 /**
  1829  * Converts all numeric HTML entities to their named counterparts.
  1933  * Converts all numeric HTML entities to their named counterparts.
  1830  *
  1934  *
  1831  * This function decodes numeric HTML entities (`&#65;` and `&#x41;`).
  1935  * This function decodes numeric HTML entities (`&#65;` and `&#x41;`).
  1832  * It doesn't do anything with named entities like `&auml;`, but we don't
  1936  * It doesn't do anything with named entities like `&auml;`, but we don't
  1833  * need them in the URL protocol whitelisting system anyway.
  1937  * need them in the allowed URL protocols system anyway.
  1834  *
  1938  *
  1835  * @since 1.0.0
  1939  * @since 1.0.0
  1836  *
  1940  *
  1837  * @param string $string Content to change entities.
  1941  * @param string $string Content to change entities.
  1838  * @return string Content after decoded entities.
  1942  * @return string Content after decoded entities.
  1973  * 'excerpt_save_pre', and 'content_filtered_save_pre' hooks.
  2077  * 'excerpt_save_pre', and 'content_filtered_save_pre' hooks.
  1974  *
  2078  *
  1975  * @since 2.0.0
  2079  * @since 2.0.0
  1976  */
  2080  */
  1977 function kses_init_filters() {
  2081 function kses_init_filters() {
  1978 	// Normal filtering
  2082 	// Normal filtering.
  1979 	add_filter( 'title_save_pre', 'wp_filter_kses' );
  2083 	add_filter( 'title_save_pre', 'wp_filter_kses' );
  1980 
  2084 
  1981 	// Comment filtering
  2085 	// Comment filtering.
  1982 	if ( current_user_can( 'unfiltered_html' ) ) {
  2086 	if ( current_user_can( 'unfiltered_html' ) ) {
  1983 		add_filter( 'pre_comment_content', 'wp_filter_post_kses' );
  2087 		add_filter( 'pre_comment_content', 'wp_filter_post_kses' );
  1984 	} else {
  2088 	} else {
  1985 		add_filter( 'pre_comment_content', 'wp_filter_kses' );
  2089 		add_filter( 'pre_comment_content', 'wp_filter_kses' );
  1986 	}
  2090 	}
  1987 
  2091 
  1988 	// Post filtering
  2092 	// Post filtering.
  1989 	add_filter( 'content_save_pre', 'wp_filter_post_kses' );
  2093 	add_filter( 'content_save_pre', 'wp_filter_post_kses' );
  1990 	add_filter( 'excerpt_save_pre', 'wp_filter_post_kses' );
  2094 	add_filter( 'excerpt_save_pre', 'wp_filter_post_kses' );
  1991 	add_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );
  2095 	add_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );
  1992 }
  2096 }
  1993 
  2097 
  2002  * hook (priority is also default).
  2106  * hook (priority is also default).
  2003  *
  2107  *
  2004  * @since 2.0.6
  2108  * @since 2.0.6
  2005  */
  2109  */
  2006 function kses_remove_filters() {
  2110 function kses_remove_filters() {
  2007 	// Normal filtering
  2111 	// Normal filtering.
  2008 	remove_filter( 'title_save_pre', 'wp_filter_kses' );
  2112 	remove_filter( 'title_save_pre', 'wp_filter_kses' );
  2009 
  2113 
  2010 	// Comment filtering
  2114 	// Comment filtering.
  2011 	remove_filter( 'pre_comment_content', 'wp_filter_post_kses' );
  2115 	remove_filter( 'pre_comment_content', 'wp_filter_post_kses' );
  2012 	remove_filter( 'pre_comment_content', 'wp_filter_kses' );
  2116 	remove_filter( 'pre_comment_content', 'wp_filter_kses' );
  2013 
  2117 
  2014 	// Post filtering
  2118 	// Post filtering.
  2015 	remove_filter( 'content_save_pre', 'wp_filter_post_kses' );
  2119 	remove_filter( 'content_save_pre', 'wp_filter_post_kses' );
  2016 	remove_filter( 'excerpt_save_pre', 'wp_filter_post_kses' );
  2120 	remove_filter( 'excerpt_save_pre', 'wp_filter_post_kses' );
  2017 	remove_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );
  2121 	remove_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );
  2018 }
  2122 }
  2019 
  2123 
  2043  * @param string $deprecated Not used.
  2147  * @param string $deprecated Not used.
  2044  * @return string Filtered string of CSS rules.
  2148  * @return string Filtered string of CSS rules.
  2045  */
  2149  */
  2046 function safecss_filter_attr( $css, $deprecated = '' ) {
  2150 function safecss_filter_attr( $css, $deprecated = '' ) {
  2047 	if ( ! empty( $deprecated ) ) {
  2151 	if ( ! empty( $deprecated ) ) {
  2048 		_deprecated_argument( __FUNCTION__, '2.8.1' ); // Never implemented
  2152 		_deprecated_argument( __FUNCTION__, '2.8.1' ); // Never implemented.
  2049 	}
  2153 	}
  2050 
  2154 
  2051 	$css = wp_kses_no_null( $css );
  2155 	$css = wp_kses_no_null( $css );
  2052 	$css = str_replace( array( "\n", "\r", "\t" ), '', $css );
  2156 	$css = str_replace( array( "\n", "\r", "\t" ), '', $css );
  2053 
  2157 
  2061 	 * @since 2.8.1
  2165 	 * @since 2.8.1
  2062 	 * @since 4.4.0 Added support for `min-height`, `max-height`, `min-width`, and `max-width`.
  2166 	 * @since 4.4.0 Added support for `min-height`, `max-height`, `min-width`, and `max-width`.
  2063 	 * @since 4.6.0 Added support for `list-style-type`.
  2167 	 * @since 4.6.0 Added support for `list-style-type`.
  2064 	 * @since 5.0.0 Added support for `background-image`.
  2168 	 * @since 5.0.0 Added support for `background-image`.
  2065 	 * @since 5.1.0 Added support for `text-transform`.
  2169 	 * @since 5.1.0 Added support for `text-transform`.
  2066 	 * @since 5.2.0 Added support for `background-position` and `grid-template-columns`
  2170 	 * @since 5.2.0 Added support for `background-position` and `grid-template-columns`.
       
  2171 	 * @since 5.3.0 Added support for `grid`, `flex` and `column` layout properties.
       
  2172 	 *              Extend `background-*` support of individual properties.
       
  2173 	 * @since 5.3.1 Added support for gradient backgrounds.
  2067 	 *
  2174 	 *
  2068 	 * @param string[] $attr Array of allowed CSS attributes.
  2175 	 * @param string[] $attr Array of allowed CSS attributes.
  2069 	 */
  2176 	 */
  2070 	$allowed_attr = apply_filters(
  2177 	$allowed_attr = apply_filters(
  2071 		'safe_style_css',
  2178 		'safe_style_css',
  2072 		array(
  2179 		array(
  2073 			'background',
  2180 			'background',
  2074 			'background-color',
  2181 			'background-color',
  2075 			'background-image',
  2182 			'background-image',
  2076 			'background-position',
  2183 			'background-position',
       
  2184 			'background-size',
       
  2185 			'background-attachment',
       
  2186 			'background-blend-mode',
  2077 
  2187 
  2078 			'border',
  2188 			'border',
       
  2189 			'border-radius',
  2079 			'border-width',
  2190 			'border-width',
  2080 			'border-color',
  2191 			'border-color',
  2081 			'border-style',
  2192 			'border-style',
  2082 			'border-right',
  2193 			'border-right',
  2083 			'border-right-color',
  2194 			'border-right-color',
  2098 
  2209 
  2099 			'border-spacing',
  2210 			'border-spacing',
  2100 			'border-collapse',
  2211 			'border-collapse',
  2101 			'caption-side',
  2212 			'caption-side',
  2102 
  2213 
       
  2214 			'columns',
       
  2215 			'column-count',
       
  2216 			'column-fill',
       
  2217 			'column-gap',
       
  2218 			'column-rule',
       
  2219 			'column-span',
       
  2220 			'column-width',
       
  2221 
  2103 			'color',
  2222 			'color',
  2104 			'font',
  2223 			'font',
  2105 			'font-family',
  2224 			'font-family',
  2106 			'font-size',
  2225 			'font-size',
  2107 			'font-style',
  2226 			'font-style',
  2132 			'padding-right',
  2251 			'padding-right',
  2133 			'padding-bottom',
  2252 			'padding-bottom',
  2134 			'padding-left',
  2253 			'padding-left',
  2135 			'padding-top',
  2254 			'padding-top',
  2136 
  2255 
       
  2256 			'flex',
       
  2257 			'flex-basis',
       
  2258 			'flex-direction',
       
  2259 			'flex-flow',
       
  2260 			'flex-grow',
       
  2261 			'flex-shrink',
       
  2262 
       
  2263 			'grid-template-columns',
       
  2264 			'grid-auto-columns',
       
  2265 			'grid-column-start',
       
  2266 			'grid-column-end',
       
  2267 			'grid-column-gap',
       
  2268 			'grid-template-rows',
       
  2269 			'grid-auto-rows',
       
  2270 			'grid-row-start',
       
  2271 			'grid-row-end',
       
  2272 			'grid-row-gap',
       
  2273 			'grid-gap',
       
  2274 
       
  2275 			'justify-content',
       
  2276 			'justify-items',
       
  2277 			'justify-self',
       
  2278 			'align-content',
       
  2279 			'align-items',
       
  2280 			'align-self',
       
  2281 
  2137 			'clear',
  2282 			'clear',
  2138 			'cursor',
  2283 			'cursor',
  2139 			'direction',
  2284 			'direction',
  2140 			'float',
  2285 			'float',
       
  2286 			'list-style-type',
  2141 			'overflow',
  2287 			'overflow',
  2142 			'vertical-align',
  2288 			'vertical-align',
  2143 			'list-style-type',
       
  2144 			'grid-template-columns',
       
  2145 		)
  2289 		)
  2146 	);
  2290 	);
  2147 
  2291 
  2148 	/*
  2292 	/*
  2149 	 * CSS attributes that accept URL data types.
  2293 	 * CSS attributes that accept URL data types.
  2161 
  2305 
  2162 		'list-style',
  2306 		'list-style',
  2163 		'list-style-image',
  2307 		'list-style-image',
  2164 	);
  2308 	);
  2165 
  2309 
       
  2310 	/*
       
  2311 	 * CSS attributes that accept gradient data types.
       
  2312 	 *
       
  2313 	 */
       
  2314 	$css_gradient_data_types = array(
       
  2315 		'background',
       
  2316 		'background-image',
       
  2317 	);
       
  2318 
  2166 	if ( empty( $allowed_attr ) ) {
  2319 	if ( empty( $allowed_attr ) ) {
  2167 		return $css;
  2320 		return $css;
  2168 	}
  2321 	}
  2169 
  2322 
  2170 	$css = '';
  2323 	$css = '';
  2171 	foreach ( $css_array as $css_item ) {
  2324 	foreach ( $css_array as $css_item ) {
  2172 		if ( $css_item == '' ) {
  2325 		if ( '' === $css_item ) {
  2173 			continue;
  2326 			continue;
  2174 		}
  2327 		}
  2175 
  2328 
  2176 		$css_item        = trim( $css_item );
  2329 		$css_item        = trim( $css_item );
  2177 		$css_test_string = $css_item;
  2330 		$css_test_string = $css_item;
  2178 		$found           = false;
  2331 		$found           = false;
  2179 		$url_attr        = false;
  2332 		$url_attr        = false;
       
  2333 		$gradient_attr   = false;
  2180 
  2334 
  2181 		if ( strpos( $css_item, ':' ) === false ) {
  2335 		if ( strpos( $css_item, ':' ) === false ) {
  2182 			$found = true;
  2336 			$found = true;
  2183 		} else {
  2337 		} else {
  2184 			$parts        = explode( ':', $css_item, 2 );
  2338 			$parts        = explode( ':', $css_item, 2 );
  2185 			$css_selector = trim( $parts[0] );
  2339 			$css_selector = trim( $parts[0] );
  2186 
  2340 
  2187 			if ( in_array( $css_selector, $allowed_attr, true ) ) {
  2341 			if ( in_array( $css_selector, $allowed_attr, true ) ) {
  2188 				$found    = true;
  2342 				$found         = true;
  2189 				$url_attr = in_array( $css_selector, $css_url_data_types, true );
  2343 				$url_attr      = in_array( $css_selector, $css_url_data_types, true );
       
  2344 				$gradient_attr = in_array( $css_selector, $css_gradient_data_types, true );
  2190 			}
  2345 			}
  2191 		}
  2346 		}
  2192 
  2347 
  2193 		if ( $found && $url_attr ) {
  2348 		if ( $found && $url_attr ) {
  2194 			// Simplified: matches the sequence `url(*)`.
  2349 			// Simplified: matches the sequence `url(*)`.
  2203 					break;
  2358 					break;
  2204 				}
  2359 				}
  2205 
  2360 
  2206 				$url = trim( $url_pieces[2] );
  2361 				$url = trim( $url_pieces[2] );
  2207 
  2362 
  2208 				if ( empty( $url ) || $url !== wp_kses_bad_protocol( $url, $allowed_protocols ) ) {
  2363 				if ( empty( $url ) || wp_kses_bad_protocol( $url, $allowed_protocols ) !== $url ) {
  2209 					$found = false;
  2364 					$found = false;
  2210 					break;
  2365 					break;
  2211 				} else {
  2366 				} else {
  2212 					// Remove the whole `url(*)` bit that was matched above from the CSS.
  2367 					// Remove the whole `url(*)` bit that was matched above from the CSS.
  2213 					$css_test_string = str_replace( $url_match, '', $css_test_string );
  2368 					$css_test_string = str_replace( $url_match, '', $css_test_string );
  2214 				}
  2369 				}
  2215 			}
  2370 			}
  2216 		}
  2371 		}
  2217 
  2372 
  2218 		// Remove any CSS containing containing \ ( & } = or comments, except for url() useage checked above.
  2373 		if ( $found && $gradient_attr ) {
  2219 		if ( $found && ! preg_match( '%[\\\(&=}]|/\*%', $css_test_string ) ) {
  2374 			$css_value = trim( $parts[1] );
  2220 			if ( $css != '' ) {
  2375 			if ( preg_match( '/^(repeating-)?(linear|radial|conic)-gradient\(([^()]|rgb[a]?\([^()]*\))*\)$/', $css_value ) ) {
  2221 				$css .= ';';
  2376 				// Remove the whole `gradient` bit that was matched above from the CSS.
       
  2377 				$css_test_string = str_replace( $css_value, '', $css_test_string );
  2222 			}
  2378 			}
  2223 
       
  2224 			$css .= $css_item;
       
  2225 		}
  2379 		}
       
  2380 
       
  2381 		if ( $found ) {
       
  2382 			// Check for any CSS containing \ ( & } = or comments, except for url() usage checked above.
       
  2383 			$allow_css = ! preg_match( '%[\\\(&=}]|/\*%', $css_test_string );
       
  2384 
       
  2385 			/**
       
  2386 			 * Filters the check for unsafe CSS in `safecss_filter_attr`.
       
  2387 			 *
       
  2388 			 * Enables developers to determine whether a section of CSS should be allowed or discarded.
       
  2389 			 * By default, the value will be false if the part contains \ ( & } = or comments.
       
  2390 			 * Return true to allow the CSS part to be included in the output.
       
  2391 			 *
       
  2392 			 * @since 5.5.0
       
  2393 			 *
       
  2394 			 * @param bool   $allow_css       Whether the CSS in the test string is considered safe.
       
  2395 			 * @param string $css_test_string The CSS string to test.
       
  2396 			 */
       
  2397 			$allow_css = apply_filters( 'safecss_filter_attr_allow_css', $allow_css, $css_test_string );
       
  2398 
       
  2399 			 // Only add the CSS part if it passes the regex check.
       
  2400 			if ( $allow_css ) {
       
  2401 				if ( '' !== $css ) {
       
  2402 					$css .= ';';
       
  2403 				}
       
  2404 
       
  2405 				$css .= $css_item;
       
  2406 			}
       
  2407 		}
  2226 	}
  2408 	}
  2227 
  2409 
  2228 	return $css;
  2410 	return $css;
  2229 }
  2411 }
  2230 
  2412 
  2231 /**
  2413 /**
  2232  * Helper function to add global attributes to a tag in the allowed html list.
  2414  * Helper function to add global attributes to a tag in the allowed HTML list.
  2233  *
  2415  *
  2234  * @since 3.5.0
  2416  * @since 3.5.0
  2235  * @since 5.0.0 Add support for `data-*` wildcard attributes.
  2417  * @since 5.0.0 Add support for `data-*` wildcard attributes.
  2236  * @access private
  2418  * @access private
  2237  * @ignore
  2419  * @ignore