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 * |
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 '>'; |
1116 return '>'; |
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]; |
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 */ |
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 { |
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( '/(�*58(?![;0-9])|�*3a(?![;a-f0-9]))/i', '$1;', $string ); |
1881 $content = preg_replace( '/(�*58(?![;0-9])|�*3a(?![;a-f0-9]))/i', '$1;', $content ); |
1762 $string2 = preg_split( '/:|�*58;|�*3a;|:/i', $string, 2 ); |
1882 $content2 = preg_split( '/:|�*58;|�*3a;|:/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…&#XYZZY;` is converted to `AT&T…&#XYZZY;`. |
1945 * example, `AT&T…&#XYZZY;` is converted to `AT&T…&#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 & |
1956 // Disarm all entities by converting & to & |
1836 $string = str_replace( '&', '&', $string ); |
1957 $content = str_replace( '&', '&', $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( '/&([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_xml_named_entities', $string ); |
1961 $content = preg_replace_callback( '/&([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_xml_named_entities', $content ); |
1841 } else { |
1962 } else { |
1842 $string = preg_replace_callback( '/&([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_named_entities', $string ); |
1963 $content = preg_replace_callback( '/&([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_named_entities', $content ); |
1843 } |
1964 } |
1844 $string = preg_replace_callback( '/&#(0*[0-9]{1,7});/', 'wp_kses_normalize_entities2', $string ); |
1965 $content = preg_replace_callback( '/&#(0*[0-9]{1,7});/', 'wp_kses_normalize_entities2', $content ); |
1845 $string = preg_replace_callback( '/&#[Xx](0*[0-9A-Fa-f]{1,6});/', 'wp_kses_normalize_entities3', $string ); |
1966 $content = preg_replace_callback( '/&#[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 * |
1977 * It doesn't do anything with named entities like `ä`, but we don't |
2103 * It doesn't do anything with named entities like `ä`, 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 * |
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 */ |
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, |