diff -r be944660c56a -r 3d72ae0968f4 wp/wp-includes/kses.php --- a/wp/wp-includes/kses.php Wed Sep 21 18:19:35 2022 +0200 +++ b/wp/wp-includes/kses.php Tue Sep 27 16:37:53 2022 +0200 @@ -81,16 +81,10 @@ 'target' => true, ), 'article' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, ), 'aside' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, ), 'audio' => array( 'autoplay' => true, @@ -101,14 +95,10 @@ 'src' => true, ), 'b' => array(), - 'bdo' => array( - 'dir' => true, - ), + 'bdo' => array(), 'big' => array(), 'blockquote' => array( - 'cite' => true, - 'lang' => true, - 'xml:lang' => true, + 'cite' => true, ), 'br' => array(), 'button' => array( @@ -120,17 +110,13 @@ 'caption' => array( 'align' => true, ), - 'cite' => array( - 'dir' => true, - 'lang' => true, - ), + 'cite' => array(), 'code' => array(), 'col' => array( 'align' => true, 'char' => true, 'charoff' => true, 'span' => true, - 'dir' => true, 'valign' => true, 'width' => true, ), @@ -148,33 +134,21 @@ 'dd' => array(), 'dfn' => array(), 'details' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'open' => true, - 'xml:lang' => true, + 'align' => true, + 'open' => true, ), 'div' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, ), 'dl' => array(), 'dt' => array(), 'em' => array(), 'fieldset' => array(), 'figure' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, ), 'figcaption' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, ), 'font' => array( 'color' => true, @@ -182,10 +156,7 @@ 'size' => true, ), 'footer' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, ), 'h1' => array( 'align' => true, @@ -206,16 +177,10 @@ 'align' => true, ), 'header' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, ), 'hgroup' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, ), 'hr' => array( 'align' => true, @@ -253,10 +218,7 @@ 'value' => true, ), 'main' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, ), 'map' => array( 'name' => true, @@ -266,16 +228,20 @@ 'type' => true, ), 'nav' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, + ), + 'object' => array( + 'data' => array( + 'required' => true, + 'value_callback' => '_wp_kses_allow_pdf_objects', + ), + 'type' => array( + 'required' => true, + 'values' => array( 'application/pdf' ), + ), ), 'p' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, ), 'pre' => array( 'width' => true, @@ -283,29 +249,25 @@ 'q' => array( 'cite' => true, ), + 'rb' => array(), + 'rp' => array(), + 'rt' => array(), + 'rtc' => array(), + 'ruby' => array(), 's' => array(), 'samp' => array(), 'span' => array( - 'dir' => true, - 'align' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, ), 'section' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, ), 'small' => array(), 'strike' => array(), 'strong' => array(), 'sub' => array(), 'summary' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, + 'align' => true, ), 'sup' => array(), 'table' => array( @@ -314,7 +276,6 @@ 'border' => true, 'cellpadding' => true, 'cellspacing' => true, - 'dir' => true, 'rules' => true, 'summary' => true, 'width' => true, @@ -333,7 +294,6 @@ 'char' => true, 'charoff' => true, 'colspan' => true, - 'dir' => true, 'headers' => true, 'height' => true, 'nowrap' => true, @@ -452,7 +412,7 @@ ); /** - * @var string[] $allowedentitynames Array of KSES allowed HTML entitity names. + * @var string[] $allowedentitynames Array of KSES allowed HTML entity names. * @since 1.0.0 */ $allowedentitynames = array( @@ -712,10 +672,10 @@ ); /** - * @var string[] $allowedxmlentitynames Array of KSES allowed XML entitity names. + * @var string[] $allowedxmlentitynames Array of KSES allowed XML entity names. * @since 5.5.0 */ - $allowedxmlnamedentities = array( + $allowedxmlentitynames = array( 'amp', 'lt', 'gt', @@ -846,22 +806,30 @@ * * @param string|array $context The context for which to retrieve tags. Allowed values are 'post', * 'strip', 'data', 'entities', or the name of a field filter such as - * 'pre_user_description'. + * 'pre_user_description', or an array of allowed HTML elements and attributes. * @return array Array of allowed HTML tags and their allowed attributes. */ function wp_kses_allowed_html( $context = '' ) { global $allowedposttags, $allowedtags, $allowedentitynames; if ( is_array( $context ) ) { + // When `$context` is an array it's actually an array of allowed HTML elements and attributes. + $html = $context; + $context = 'explicit'; + /** - * Filters the HTML that is allowed for a given context. + * Filters the HTML tags that are allowed for a given context. + * + * HTML tags and attribute names are case-insensitive in HTML but must be + * added to the KSES allow list in lowercase. An item added to the allow list + * in upper or mixed case will not recognized as permitted by KSES. * * @since 3.5.0 * - * @param array[]|string $context Context to judge allowed tags by. - * @param string $context_type Context name. + * @param array[] $html Allowed HTML tags. + * @param string $context Context name. */ - return apply_filters( 'wp_kses_allowed_html', $context, 'explicit' ); + return apply_filters( 'wp_kses_allowed_html', $html, $context ); } switch ( $context ) { @@ -1040,7 +1008,7 @@ * or a context name such as 'post'. * @global string[] $pass_allowed_protocols Array of allowed URL protocols. * - * @param array $matches preg_replace regexp matches + * @param array $match preg_replace regexp matches * @return string */ function _wp_kses_split_callback( $match ) { @@ -1126,12 +1094,22 @@ * Removes all attributes, if none are allowed for this element. * * If some are allowed it calls `wp_kses_hair()` to split them further, and then - * it builds up new HTML code from the data that `kses_hair()` returns. It also + * it builds up new HTML code from the data that `wp_kses_hair()` returns. It also * removes `<` and `>` characters, if there are any left. One more thing it does * is to check if the tag has a closing XHTML slash, and if it does, it puts one * in the returned code as well. * + * An array of allowed values can be defined for attributes. If the attribute value + * doesn't fall into the list, the attribute will be removed from the tag. + * + * Attributes can be marked as required. If a required attribute is not present, + * KSES will remove all attributes from the tag. As KSES doesn't match opening and + * closing tags, it's not possible to safely remove the tag itself, the safest + * fallback is to strip all attributes from the tag, instead. + * * @since 1.0.0 + * @since 5.9.0 Added support for an array of allowed values for attributes. + * Added support for required attributes. * * @param string $element HTML element/tag. * @param string $attr HTML attributes from HTML element to closing HTML element tag. @@ -1161,15 +1139,48 @@ // Split it. $attrarr = wp_kses_hair( $attr, $allowed_protocols ); - // Go through $attrarr, and save the allowed attributes for this element - // in $attr2. + // Check if there are attributes that are required. + $required_attrs = array_filter( + $allowed_html[ $element_low ], + function( $required_attr_limits ) { + return isset( $required_attr_limits['required'] ) && true === $required_attr_limits['required']; + } + ); + + /* + * If a required attribute check fails, we can return nothing for a self-closing tag, + * but for a non-self-closing tag the best option is to return the element with attributes, + * as KSES doesn't handle matching the relevant closing tag. + */ + $stripped_tag = ''; + if ( empty( $xhtml_slash ) ) { + $stripped_tag = "<$element>"; + } + + // Go through $attrarr, and save the allowed attributes for this element in $attr2. $attr2 = ''; foreach ( $attrarr as $arreach ) { + // Check if this attribute is required. + $required = isset( $required_attrs[ strtolower( $arreach['name'] ) ] ); + if ( wp_kses_attr_check( $arreach['name'], $arreach['value'], $arreach['whole'], $arreach['vless'], $element, $allowed_html ) ) { $attr2 .= ' ' . $arreach['whole']; + + // If this was a required attribute, we can mark it as found. + if ( $required ) { + unset( $required_attrs[ strtolower( $arreach['name'] ) ] ); + } + } elseif ( $required ) { + // This attribute was required, but didn't pass the check. The entire tag is not allowed. + return $stripped_tag; } } + // If some required attributes weren't set, the entire tag is not allowed. + if ( ! empty( $required_attrs ) ) { + return $stripped_tag; + } + // Remove any "<" or ">" characters. $attr2 = preg_replace( '/[<>]/', '', $attr2 ); @@ -1180,7 +1191,7 @@ * Determines whether an attribute is allowed. * * @since 4.2.3 - * @since 5.0.0 Add support for `data-*` wildcard attributes. + * @since 5.0.0 Added support for `data-*` wildcard attributes. * * @param string $name The attribute name. Passed by reference. Returns empty string when not allowed. * @param string $value The attribute value. Passed by reference. Returns a filtered value. @@ -1214,7 +1225,9 @@ * Note: the attribute name should only contain `A-Za-z0-9_-` chars, * double hyphens `--` are not accepted by WordPress. */ - if ( strpos( $name_low, 'data-' ) === 0 && ! empty( $allowed_attr['data-*'] ) && preg_match( '/^data(?:-[a-z0-9_]+)+$/', $name_low, $match ) ) { + if ( strpos( $name_low, 'data-' ) === 0 && ! empty( $allowed_attr['data-*'] ) + && preg_match( '/^data(?:-[a-z0-9_]+)+$/', $name_low, $match ) + ) { /* * Add the whole attribute name to the allowed attributes and set any restrictions * for the `data-*` attribute values for the current element. @@ -1596,6 +1609,28 @@ $ok = false; } break; + + case 'values': + /* + * The values check is used when you want to make sure that the attribute + * has one of the given values. + */ + + if ( false === array_search( strtolower( $value ), $checkvalue, true ) ) { + $ok = false; + } + break; + + case 'value_callback': + /* + * The value_callback check is used when you want to make sure that the attribute + * value is accepted by the callback function. + */ + + if ( ! call_user_func( $checkvalue, $value ) ) { + $ok = false; + } + break; } // End switch. return $ok; @@ -1846,13 +1881,13 @@ * @since 5.5.0 * * @global array $allowedentitynames - * @global array $allowedxmlnamedentities + * @global array $allowedxmlentitynames * * @param array $matches preg_replace_callback() matches array. * @return string Correctly encoded entity. */ function wp_kses_xml_named_entities( $matches ) { - global $allowedentitynames, $allowedxmlnamedentities; + global $allowedentitynames, $allowedxmlentitynames; if ( empty( $matches[1] ) ) { return ''; @@ -1860,7 +1895,7 @@ $i = $matches[1]; - if ( in_array( $i, $allowedxmlnamedentities, true ) ) { + if ( in_array( $i, $allowedxmlentitynames, true ) ) { return "&$i;"; } elseif ( in_array( $i, $allowedentitynames, true ) ) { return html_entity_decode( "&$i;", ENT_HTML5 ); @@ -2028,6 +2063,33 @@ } /** + * Sanitizes global styles user content removing unsafe rules. + * + * @since 5.9.0 + * + * @param string $data Post content to filter. + * @return string Filtered post content with unsafe rules removed. + */ +function wp_filter_global_styles_post( $data ) { + $decoded_data = json_decode( wp_unslash( $data ), true ); + $json_decoding_error = json_last_error(); + if ( + JSON_ERROR_NONE === $json_decoding_error && + is_array( $decoded_data ) && + isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && + $decoded_data['isGlobalStylesUserThemeJSON'] + ) { + unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); + + $data_to_encode = WP_Theme_JSON::remove_insecure_properties( $decoded_data ); + + $data_to_encode['isGlobalStylesUserThemeJSON'] = true; + return wp_slash( wp_json_encode( $data_to_encode ) ); + } + return $data; +} + +/** * Sanitizes content for allowed HTML tags for post content. * * Post content refers to the page contents of the 'post' type and not `$_POST` @@ -2095,6 +2157,10 @@ add_filter( 'pre_comment_content', 'wp_filter_kses' ); } + // Global Styles filtering: Global Styles filters should be executed before normal post_kses HTML filters. + add_filter( 'content_save_pre', 'wp_filter_global_styles_post', 9 ); + add_filter( 'content_filtered_save_pre', 'wp_filter_global_styles_post', 9 ); + // Post filtering. add_filter( 'content_save_pre', 'wp_filter_post_kses' ); add_filter( 'excerpt_save_pre', 'wp_filter_post_kses' ); @@ -2121,6 +2187,10 @@ remove_filter( 'pre_comment_content', 'wp_filter_post_kses' ); remove_filter( 'pre_comment_content', 'wp_filter_kses' ); + // Global Styles filtering. + remove_filter( 'content_save_pre', 'wp_filter_global_styles_post', 9 ); + remove_filter( 'content_filtered_save_pre', 'wp_filter_global_styles_post', 9 ); + // Post filtering. remove_filter( 'content_save_pre', 'wp_filter_post_kses' ); remove_filter( 'excerpt_save_pre', 'wp_filter_post_kses' ); @@ -2148,6 +2218,16 @@ * Filters an inline style attribute and removes disallowed rules. * * @since 2.8.1 + * @since 4.4.0 Added support for `min-height`, `max-height`, `min-width`, and `max-width`. + * @since 4.6.0 Added support for `list-style-type`. + * @since 5.0.0 Added support for `background-image`. + * @since 5.1.0 Added support for `text-transform`. + * @since 5.2.0 Added support for `background-position` and `grid-template-columns`. + * @since 5.3.0 Added support for `grid`, `flex` and `column` layout properties. + * Extend `background-*` support of individual properties. + * @since 5.3.1 Added support for gradient backgrounds. + * @since 5.7.1 Added support for `object-position`. + * @since 5.8.0 Added support for `calc()` and `var()` values. * * @param string $css A string of CSS rules. * @param string $deprecated Not used. @@ -2166,19 +2246,9 @@ $css_array = explode( ';', trim( $css ) ); /** - * Filters list of allowed CSS attributes. + * Filters the list of allowed CSS attributes. * * @since 2.8.1 - * @since 4.4.0 Added support for `min-height`, `max-height`, `min-width`, and `max-width`. - * @since 4.6.0 Added support for `list-style-type`. - * @since 5.0.0 Added support for `background-image`. - * @since 5.1.0 Added support for `text-transform`. - * @since 5.2.0 Added support for `background-position` and `grid-template-columns`. - * @since 5.3.0 Added support for `grid`, `flex` and `column` layout properties. - * Extend `background-*` support of individual properties. - * @since 5.3.1 Added support for gradient backgrounds. - * @since 5.7.1 Added support for `object-position`. - * @since 5.8.0 Added support for `calc()` and `var()` values. * * @param string[] $attr Array of allowed CSS attributes. */ @@ -2204,16 +2274,24 @@ 'border-right-width', 'border-bottom', 'border-bottom-color', + 'border-bottom-left-radius', + 'border-bottom-right-radius', 'border-bottom-style', 'border-bottom-width', + 'border-bottom-right-radius', + 'border-bottom-left-radius', 'border-left', 'border-left-color', 'border-left-style', 'border-left-width', 'border-top', 'border-top-color', + 'border-top-left-radius', + 'border-top-right-radius', 'border-top-style', 'border-top-width', + 'border-top-left-radius', + 'border-top-right-radius', 'border-spacing', 'border-collapse', @@ -2228,6 +2306,7 @@ 'column-width', 'color', + 'filter', 'font', 'font-family', 'font-size', @@ -2411,7 +2490,7 @@ */ $allow_css = apply_filters( 'safecss_filter_attr_allow_css', $allow_css, $css_test_string ); - // Only add the CSS part if it passes the regex check. + // Only add the CSS part if it passes the regex check. if ( $allow_css ) { if ( '' !== $css ) { $css .= ';'; @@ -2429,7 +2508,9 @@ * Helper function to add global attributes to a tag in the allowed HTML list. * * @since 3.5.0 - * @since 5.0.0 Add support for `data-*` wildcard attributes. + * @since 5.0.0 Added support for `data-*` wildcard attributes. + * @since 6.0.0 Added `dir`, `lang`, and `xml:lang` to global attributes. + * * @access private * @ignore * @@ -2444,11 +2525,14 @@ 'aria-labelledby' => true, 'aria-hidden' => true, 'class' => true, + 'data-*' => true, + 'dir' => true, 'id' => true, + 'lang' => true, 'style' => true, 'title' => true, 'role' => true, - 'data-*' => true, + 'xml:lang' => true, ); if ( true === $value ) { @@ -2461,3 +2545,39 @@ return $value; } + +/** + * Helper function to check if this is a safe PDF URL. + * + * @since 5.9.0 + * @access private + * @ignore + * + * @param string $url The URL to check. + * @return bool True if the URL is safe, false otherwise. + */ +function _wp_kses_allow_pdf_objects( $url ) { + // We're not interested in URLs that contain query strings or fragments. + if ( str_contains( $url, '?' ) || str_contains( $url, '#' ) ) { + return false; + } + + // If it doesn't have a PDF extension, it's not safe. + if ( ! str_ends_with( $url, '.pdf' ) ) { + return false; + } + + // If the URL host matches the current site's media URL, it's safe. + $upload_info = wp_upload_dir( null, false ); + $parsed_url = wp_parse_url( $upload_info['url'] ); + $upload_host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : ''; + $upload_port = isset( $parsed_url['port'] ) ? ':' . $parsed_url['port'] : ''; + + if ( str_starts_with( $url, "http://$upload_host$upload_port/" ) + || str_starts_with( $url, "https://$upload_host$upload_port/" ) + ) { + return true; + } + + return false; +}