X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/af50974463450c98503e763a7836a50e260461a9..refs/tags/wordpress-4.3:/wp-includes/kses.php diff --git a/wp-includes/kses.php b/wp-includes/kses.php index cd2727a4..578c5ba4 100644 --- a/wp-includes/kses.php +++ b/wp-includes/kses.php @@ -441,6 +441,7 @@ if ( ! CUSTOM_TAGS ) { 'q' => array( 'cite' => true, ), + 's' => array(), 'strike' => array(), 'strong' => array(), ); @@ -512,28 +513,108 @@ if ( ! CUSTOM_TAGS ) { * * @since 1.0.0 * - * @param string $string Content to filter through kses - * @param array $allowed_html List of allowed HTML elements - * @param array $allowed_protocols Optional. Allowed protocol in links. + * @param string $string Content to filter through kses + * @param array $allowed_html List of allowed HTML elements + * @param array $allowed_protocols Optional. Allowed protocol in links. * @return string Filtered content with only allowed HTML elements */ function wp_kses( $string, $allowed_html, $allowed_protocols = array() ) { if ( empty( $allowed_protocols ) ) $allowed_protocols = wp_allowed_protocols(); - $string = wp_kses_no_null($string); + $string = wp_kses_no_null( $string, array( 'slash_zero' => 'keep' ) ); $string = wp_kses_js_entities($string); $string = wp_kses_normalize_entities($string); $string = wp_kses_hook($string, $allowed_html, $allowed_protocols); // WP changed the order of these funcs and added args to wp_kses_hook return wp_kses_split($string, $allowed_html, $allowed_protocols); } +/** + * Filters one attribute only and ensures its value is allowed. + * + * This function has the advantage of being more secure than esc_attr() and can + * escape data in some situations where wp_kses() must strip the whole attribute. + * + * @since 4.2.3 + * + * @param string $string The 'whole' attribute, including name and value. + * @param string $element The element name to which the attribute belongs. + * @return string Filtered attribute. + */ +function wp_kses_one_attr( $string, $element ) { + $uris = array('xmlns', 'profile', 'href', 'src', 'cite', 'classid', 'codebase', 'data', 'usemap', 'longdesc', 'action'); + $allowed_html = wp_kses_allowed_html( 'post' ); + $allowed_protocols = wp_allowed_protocols(); + $string = wp_kses_no_null( $string, array( 'slash_zero' => 'keep' ) ); + $string = wp_kses_js_entities( $string ); + + // Preserve leading and trailing whitespace. + $matches = array(); + preg_match('/^\s*/', $string, $matches); + $lead = $matches[0]; + preg_match('/\s*$/', $string, $matches); + $trail = $matches[0]; + if ( empty( $trail ) ) { + $string = substr( $string, strlen( $lead ) ); + } else { + $string = substr( $string, strlen( $lead ), -strlen( $trail ) ); + } + + // Parse attribute name and value from input. + $split = preg_split( '/\s*=\s*/', $string, 2 ); + $name = $split[0]; + if ( count( $split ) == 2 ) { + $value = $split[1]; + + // Remove quotes surrounding $value. + // Also guarantee correct quoting in $string for this one attribute. + if ( '' == $value ) { + $quote = ''; + } else { + $quote = $value[0]; + } + if ( '"' == $quote || "'" == $quote ) { + if ( substr( $value, -1 ) != $quote ) { + return ''; + } + $value = substr( $value, 1, -1 ); + } else { + $quote = '"'; + } + + // Sanitize quotes, angle braces, and entities. + $value = esc_attr( $value ); + + // Sanitize URI values. + if ( in_array( strtolower( $name ), $uris ) ) { + $value = wp_kses_bad_protocol( $value, $allowed_protocols ); + } + + $string = "$name=$quote$value$quote"; + $vless = 'n'; + } else { + $value = ''; + $vless = 'y'; + } + + // Sanitize attribute by name. + wp_kses_attr_check( $name, $value, $string, $vless, $element, $allowed_html ); + + // Restore whitespace. + return $lead . $string . $trail; +} + /** * Return a list of allowed tags and attributes for a given context. * * @since 3.5.0 * - * @param string $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. + * @global array $allowedposttags + * @global array $allowedtags + * @global array $allowedentitynames + * + * @param string $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. * @return array List of allowed tags and their allowed attributes. */ function wp_kses_allowed_html( $context = '' ) { @@ -556,22 +637,22 @@ function wp_kses_allowed_html( $context = '' ) { case 'post': /** This filter is documented in wp-includes/kses.php */ return apply_filters( 'wp_kses_allowed_html', $allowedposttags, $context ); - break; + case 'user_description': case 'pre_user_description': $tags = $allowedtags; $tags['a']['rel'] = true; /** This filter is documented in wp-includes/kses.php */ return apply_filters( 'wp_kses_allowed_html', $tags, $context ); - break; + case 'strip': /** This filter is documented in wp-includes/kses.php */ return apply_filters( 'wp_kses_allowed_html', array(), $context ); - break; + case 'entities': /** This filter is documented in wp-includes/kses.php */ return apply_filters( 'wp_kses_allowed_html', $allowedentitynames, $context); - break; + case 'data': default: /** This filter is documented in wp-includes/kses.php */ @@ -587,9 +668,9 @@ function wp_kses_allowed_html( $context = '' ) { * * @since 1.0.0 * - * @param string $string Content to filter through kses - * @param array $allowed_html List of allowed HTML elements - * @param array $allowed_protocols Allowed protocol in links + * @param string $string Content to filter through kses + * @param array $allowed_html List of allowed HTML elements + * @param array $allowed_protocols Allowed protocol in links * @return string Filtered content through 'pre_kses' hook */ function wp_kses_hook( $string, $allowed_html, $allowed_protocols ) { @@ -602,8 +683,7 @@ function wp_kses_hook( $string, $allowed_html, $allowed_protocols ) { * @param array $allowed_html Allowed HTML elements. * @param array $allowed_protocols Allowed protocol in links. */ - $string = apply_filters( 'pre_kses', $string, $allowed_html, $allowed_protocols ); - return $string; + return apply_filters( 'pre_kses', $string, $allowed_html, $allowed_protocols ); } /** @@ -624,9 +704,12 @@ function wp_kses_version() { * * @since 1.0.0 * - * @param string $string Content to filter - * @param array $allowed_html Allowed HTML elements - * @param array $allowed_protocols Allowed protocols to keep + * @global array $pass_allowed_html + * @global array $pass_allowed_protocols + * + * @param string $string Content to filter + * @param array $allowed_html Allowed HTML elements + * @param array $allowed_protocols Allowed protocols to keep * @return string Content with fixed HTML tags */ function wp_kses_split( $string, $allowed_html, $allowed_protocols ) { @@ -641,6 +724,11 @@ function wp_kses_split( $string, $allowed_html, $allowed_protocols ) { * * @since 3.1.0 * @access private + * + * @global array $pass_allowed_html + * @global array $pass_allowed_protocols + * + * @return string */ function _wp_kses_split_callback( $match ) { global $pass_allowed_html, $pass_allowed_protocols; @@ -662,9 +750,9 @@ function _wp_kses_split_callback( $match ) { * @access private * @since 1.0.0 * - * @param string $string Content to filter - * @param array $allowed_html Allowed HTML elements - * @param array $allowed_protocols Allowed protocols to keep + * @param string $string Content to filter + * @param array $allowed_html Allowed HTML elements + * @param array $allowed_protocols Allowed protocols to keep * @return string Fixed HTML element */ function wp_kses_split2($string, $allowed_html, $allowed_protocols) { @@ -672,7 +760,7 @@ function wp_kses_split2($string, $allowed_html, $allowed_protocols) { if (substr($string, 0, 1) != '<') return '>'; - # It matched a ">" character + // It matched a ">" character if ( ''), '', $string ); @@ -686,11 +774,11 @@ function wp_kses_split2($string, $allowed_html, $allowed_protocols) { $string = preg_replace('/-$/', '', $string); return ""; } - # Allow HTML comments + // Allow HTML comments if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $string, $matches)) return ''; - # It's seriously malformed + // It's seriously malformed $slash = trim($matches[1]); $elem = $matches[2]; @@ -701,11 +789,11 @@ function wp_kses_split2($string, $allowed_html, $allowed_protocols) { if ( ! isset($allowed_html[strtolower($elem)]) ) return ''; - # They are using a not allowed HTML element + // They are using a not allowed HTML element if ($slash != '') return ""; - # No attributes are allowed for closing elements + // No attributes are allowed for closing elements return wp_kses_attr( $elem, $attrlist, $allowed_html, $allowed_protocols ); } @@ -721,76 +809,88 @@ function wp_kses_split2($string, $allowed_html, $allowed_protocols) { * * @since 1.0.0 * - * @param string $element HTML element/tag - * @param string $attr HTML attributes from HTML element to closing HTML element tag - * @param array $allowed_html Allowed HTML elements - * @param array $allowed_protocols Allowed protocols to keep + * @param string $element HTML element/tag + * @param string $attr HTML attributes from HTML element to closing HTML element tag + * @param array $allowed_html Allowed HTML elements + * @param array $allowed_protocols Allowed protocols to keep * @return string Sanitized HTML element */ function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) { - # Is there a closing XHTML slash at the end of the attributes? - if ( ! is_array( $allowed_html ) ) $allowed_html = wp_kses_allowed_html( $allowed_html ); + // Is there a closing XHTML slash at the end of the attributes? $xhtml_slash = ''; if (preg_match('%\s*/\s*$%', $attr)) $xhtml_slash = ' /'; - # Are any attributes allowed at all for this element? + // Are any attributes allowed at all for this element? if ( ! isset($allowed_html[strtolower($element)]) || count($allowed_html[strtolower($element)]) == 0 ) return "<$element$xhtml_slash>"; - # Split it + // Split it $attrarr = wp_kses_hair($attr, $allowed_protocols); - # Go through $attrarr, and save the allowed attributes for this element - # in $attr2 + // Go through $attrarr, and save the allowed attributes for this element + // in $attr2 $attr2 = ''; + foreach ( $attrarr as $arreach ) { + if ( wp_kses_attr_check( $arreach['name'], $arreach['value'], $arreach['whole'], $arreach['vless'], $element, $allowed_html ) ) { + $attr2 .= ' '.$arreach['whole']; + } + } - $allowed_attr = $allowed_html[strtolower($element)]; - foreach ($attrarr as $arreach) { - if ( ! isset( $allowed_attr[strtolower($arreach['name'])] ) ) - continue; # the attribute is not allowed + // Remove any "<" or ">" characters + $attr2 = preg_replace('/[<>]/', '', $attr2); - $current = $allowed_attr[strtolower($arreach['name'])]; - if ( $current == '' ) - continue; # the attribute is not allowed + return "<$element$attr2$xhtml_slash>"; +} - if ( strtolower( $arreach['name'] ) == 'style' ) { - $orig_value = $arreach['value']; - $value = safecss_filter_attr( $orig_value ); +/** + * Determine whether an attribute is allowed. + * + * @since 4.2.3 + * + * @param string $name The attribute name. Returns empty string when not allowed. + * @param string $value The attribute value. Returns a filtered value. + * @param string $whole The name=value input. Returns filtered input. + * @param string $vless 'y' when attribute like "enabled", otherwise 'n'. + * @param string $element The name of the element to which this attribute belongs. + * @param array $allowed_html The full list of allowed elements and attributes. + * @return bool Is the attribute allowed? + */ +function wp_kses_attr_check( &$name, &$value, &$whole, $vless, $element, $allowed_html ) { + $allowed_attr = $allowed_html[strtolower( $element )]; + + $name_low = strtolower( $name ); + if ( ! isset( $allowed_attr[$name_low] ) || '' == $allowed_attr[$name_low] ) { + $name = $value = $whole = ''; + return false; + } - if ( empty( $value ) ) - continue; + if ( 'style' == $name_low ) { + $new_value = safecss_filter_attr( $value ); - $arreach['value'] = $value; - $arreach['whole'] = str_replace( $orig_value, $value, $arreach['whole'] ); + if ( empty( $new_value ) ) { + $name = $value = $whole = ''; + return false; } - if ( ! is_array($current) ) { - $attr2 .= ' '.$arreach['whole']; - # there are no checks + $whole = str_replace( $value, $new_value, $whole ); + $value = $new_value; + } - } else { - # there are some checks - $ok = true; - foreach ($current as $currkey => $currval) { - if ( ! wp_kses_check_attr_val($arreach['value'], $arreach['vless'], $currkey, $currval) ) { - $ok = false; - break; - } + if ( is_array( $allowed_attr[$name_low] ) ) { + // there are some checks + foreach ( $allowed_attr[$name_low] as $currkey => $currval ) { + if ( ! wp_kses_check_attr_val( $value, $vless, $currkey, $currval ) ) { + $name = $value = $whole = ''; + return false; } + } + } - if ( $ok ) - $attr2 .= ' '.$arreach['whole']; # it passed them - } # if !is_array($current) - } # foreach - - # Remove any "<" or ">" characters - $attr2 = preg_replace('/[<>]/', '', $attr2); - - return "<$element$attr2$xhtml_slash>"; + return true; } /** @@ -806,8 +906,8 @@ function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) { * * @since 1.0.0 * - * @param string $attr Attribute list from HTML element to closing HTML element tag - * @param array $allowed_protocols Allowed protocols to keep + * @param string $attr Attribute list from HTML element to closing HTML element tag + * @param array $allowed_protocols Allowed protocols to keep * @return array List of attributes after parsing */ function wp_kses_hair($attr, $allowed_protocols) { @@ -816,13 +916,13 @@ function wp_kses_hair($attr, $allowed_protocols) { $attrname = ''; $uris = array('xmlns', 'profile', 'href', 'src', 'cite', 'classid', 'codebase', 'data', 'usemap', 'longdesc', 'action'); - # Loop through the whole attribute list + // Loop through the whole attribute list while (strlen($attr) != 0) { - $working = 0; # Was the last operation successful? + $working = 0; // Was the last operation successful? switch ($mode) { - case 0 : # attribute name, href for instance + case 0 : // attribute name, href for instance if ( preg_match('/^([-a-zA-Z:]+)/', $attr, $match ) ) { $attrname = $match[1]; @@ -832,9 +932,9 @@ function wp_kses_hair($attr, $allowed_protocols) { break; - case 1 : # equals sign or valueless ("selected") + case 1 : // equals sign or valueless ("selected") - if (preg_match('/^\s*=\s*/', $attr)) # equals sign + if (preg_match('/^\s*=\s*/', $attr)) // equals sign { $working = 1; $mode = 2; @@ -842,7 +942,7 @@ function wp_kses_hair($attr, $allowed_protocols) { break; } - if (preg_match('/^\s+/', $attr)) # valueless + if (preg_match('/^\s+/', $attr)) // valueless { $working = 1; $mode = 0; @@ -854,10 +954,10 @@ function wp_kses_hair($attr, $allowed_protocols) { break; - case 2 : # attribute value, a URL after href= for instance + case 2 : // attribute value, a URL after href= for instance if (preg_match('%^"([^"]*)"(\s+|/?$)%', $attr, $match)) - # "value" + // "value" { $thisval = $match[1]; if ( in_array(strtolower($attrname), $uris) ) @@ -873,7 +973,7 @@ function wp_kses_hair($attr, $allowed_protocols) { } if (preg_match("%^'([^']*)'(\s+|/?$)%", $attr, $match)) - # 'value' + // 'value' { $thisval = $match[1]; if ( in_array(strtolower($attrname), $uris) ) @@ -889,7 +989,7 @@ function wp_kses_hair($attr, $allowed_protocols) { } if (preg_match("%^([^\s\"']+)(\s+|/?$)%", $attr, $match)) - # value + // value { $thisval = $match[1]; if ( in_array(strtolower($attrname), $uris) ) @@ -898,30 +998,133 @@ function wp_kses_hair($attr, $allowed_protocols) { if(false === array_key_exists($attrname, $attrarr)) { $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n'); } - # We add quotes to conform to W3C's HTML spec. + // We add quotes to conform to W3C's HTML spec. $working = 1; $mode = 0; $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr); } break; - } # switch + } // switch - if ($working == 0) # not well formed, remove and try again + if ($working == 0) // not well formed, remove and try again { $attr = wp_kses_html_error($attr); $mode = 0; } - } # while + } // while if ($mode == 1 && false === array_key_exists($attrname, $attrarr)) - # special case, for when the attribute list ends with a valueless - # attribute like "selected" + // special case, for when the attribute list ends with a valueless + // attribute like "selected" $attrarr[$attrname] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y'); return $attrarr; } +/** + * Finds all attributes of an HTML element. + * + * Does not modify input. May return "evil" output. + * + * Based on wp_kses_split2() and wp_kses_attr() + * + * @since 4.2.3 + * + * @param string $element HTML element/tag + * @return array|bool List of attributes found in $element. Returns false on failure. + */ +function wp_kses_attr_parse( $element ) { + $valid = preg_match('%^(<\s*)(/\s*)?([a-zA-Z0-9]+\s*)([^>]*)(>?)$%', $element, $matches); + if ( 1 !== $valid ) { + return false; + } + + $begin = $matches[1]; + $slash = $matches[2]; + $elname = $matches[3]; + $attr = $matches[4]; + $end = $matches[5]; + + if ( '' !== $slash ) { + // Closing elements do not get parsed. + return false; + } + + // Is there a closing XHTML slash at the end of the attributes? + if ( 1 === preg_match( '%\s*/\s*$%', $attr, $matches ) ) { + $xhtml_slash = $matches[0]; + $attr = substr( $attr, 0, -strlen( $xhtml_slash ) ); + } else { + $xhtml_slash = ''; + } + + // Split it + $attrarr = wp_kses_hair_parse( $attr ); + if ( false === $attrarr ) { + return false; + } + + // Make sure all input is returned by adding front and back matter. + array_unshift( $attrarr, $begin . $slash . $elname ); + array_push( $attrarr, $xhtml_slash . $end ); + + return $attrarr; +} + +/** + * Builds an attribute list from string containing attributes. + * + * Does not modify input. May return "evil" output. + * In case of unexpected input, returns false instead of stripping things. + * + * Based on wp_kses_hair() but does not return a multi-dimensional array. + * + * @since 4.2.3 + * + * @param string $attr Attribute list from HTML element to closing HTML element tag + * @return array|bool List of attributes found in $attr. Returns false on failure. + */ +function wp_kses_hair_parse( $attr ) { + if ( '' === $attr ) { + return array(); + } + + $regex = + '(?:' + . '[-a-zA-Z:]+' // Attribute name. + . '|' + . '\[\[?[^\[\]]+\]\]?' // Shortcode in the name position implies unfiltered_html. + . ')' + . '(?:' // Attribute value. + . '\s*=\s*' // All values begin with '=' + . '(?:' + . '"[^"]*"' // Double-quoted + . '|' + . "'[^']*'" // Single-quoted + . '|' + . '[^\s"\']+' // Non-quoted + . '(?:\s|$)' // Must have a space + . ')' + . '|' + . '(?:\s|$)' // If attribute has no value, space is required. + . ')' + . '\s*'; // Trailing space is optional except as mentioned above. + + // Although it is possible to reduce this procedure to a single regexp, + // we must run that regexp twice to get exactly the expected result. + + $validation = "%^($regex)+$%"; + $extraction = "%$regex%"; + + if ( 1 === preg_match( $validation, $attr ) ) { + preg_match_all( $extraction, $attr, $attrarr ); + return $attrarr[0]; + } else { + return false; + } +} + /** * Performs different checks for attribute values. * @@ -930,10 +1133,10 @@ function wp_kses_hair($attr, $allowed_protocols) { * * @since 1.0.0 * - * @param string $value Attribute value - * @param string $vless Whether the value is valueless. Use 'y' or 'n' - * @param string $checkname What $checkvalue is checking for. - * @param mixed $checkvalue What constraint the value should pass + * @param string $value Attribute value + * @param string $vless Whether the value is valueless. Use 'y' or 'n' + * @param string $checkname What $checkvalue is checking for. + * @param mixed $checkvalue What constraint the value should pass * @return bool Whether check passes */ function wp_kses_check_attr_val($value, $vless, $checkname, $checkvalue) { @@ -941,28 +1144,28 @@ function wp_kses_check_attr_val($value, $vless, $checkname, $checkvalue) { switch (strtolower($checkname)) { case 'maxlen' : - # The maxlen check makes sure that the attribute value has a length not - # greater than the given value. This can be used to avoid Buffer Overflows - # in WWW clients and various Internet servers. + // The maxlen check makes sure that the attribute value has a length not + // greater than the given value. This can be used to avoid Buffer Overflows + // in WWW clients and various Internet servers. if (strlen($value) > $checkvalue) $ok = false; break; case 'minlen' : - # The minlen check makes sure that the attribute value has a length not - # smaller than the given value. + // The minlen check makes sure that the attribute value has a length not + // smaller than the given value. if (strlen($value) < $checkvalue) $ok = false; break; case 'maxval' : - # The maxval check does two things: it checks that the attribute value is - # an integer from 0 and up, without an excessive amount of zeroes or - # whitespace (to avoid Buffer Overflows). It also checks that the attribute - # value is not greater than the given value. - # This check can be used to avoid Denial of Service attacks. + // The maxval check does two things: it checks that the attribute value is + // an integer from 0 and up, without an excessive amount of zeroes or + // whitespace (to avoid Buffer Overflows). It also checks that the attribute + // value is not greater than the given value. + // This check can be used to avoid Denial of Service attacks. if (!preg_match('/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value)) $ok = false; @@ -971,8 +1174,8 @@ function wp_kses_check_attr_val($value, $vless, $checkname, $checkvalue) { break; case 'minval' : - # The minval check makes sure that the attribute value is a positive integer, - # and that it is not smaller than the given value. + // The minval check makes sure that the attribute value is a positive integer, + // and that it is not smaller than the given value. if (!preg_match('/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value)) $ok = false; @@ -981,15 +1184,15 @@ function wp_kses_check_attr_val($value, $vless, $checkname, $checkvalue) { break; case 'valueless' : - # The valueless check makes sure if the attribute has a value - # (like ) or not () or not (