Make WordPress Core

Changeset 46907


Ignore:
Timestamp:
12/12/2019 06:36:20 PM (5 years ago)
Author:
whyisjake
Message:

Ensure that a user can publish_posts before making a post sticky.
Props: danielbachhuber, whyisjake, peterwilson, xknown.
Prevent stored XSS through wp_targeted_link_rel().
Props: vortfu, whyisjake, peterwilsoncc, xknown, SergeyBiryukov, flaviozavan.
Update wp_kses_bad_protocol() to recognize : on uri attributes,
wp_kses_bad_protocol() makes sure to validate that uri attributes don't contain invalid/or not allowed protocols. While this works fine in most cases, there's a risk that by using the colon html5 named entity, one is able to bypass this function.
Brings r46895 to the 5.3 branch.
Props: xknown, nickdaugherty, peterwilsoncc.
Prevent stored XSS in the block editor.
Brings r46896 to the 5.3 branch.
Prevent escaped unicode characters become unescaped in unsafe HTML during JSON decoding.
Props: aduth, epiqueras.

Location:
branches/5.1
Files:
2 added
9 edited

Legend:

Unmodified
Added
Removed
  • branches/5.1

  • branches/5.1/src/wp-includes/blocks.php

    r44576 r46907  
    7575 * @see parse_blocks()
    7676 *
    77  * @param string                  $block_type Full Block type to look for.
     77 * @param string                  $block_name Full Block type to look for.
    7878 * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post.
    7979 * @return bool Whether the post content contains the specified block.
    8080 */
    81 function has_block( $block_type, $post = null ) {
     81function has_block( $block_name, $post = null ) {
    8282    if ( ! has_blocks( $post ) ) {
    8383        return false;
     
    9191    }
    9292
    93     return false !== strpos( $post, '<!-- wp:' . $block_type . ' ' );
     93    /*
     94     * Normalize block name to include namespace, if provided as non-namespaced.
     95     * This matches behavior for WordPress 5.0.0 - 5.3.0 in matching blocks by
     96     * their serialized names.
     97     */
     98    if ( false === strpos( $block_name, '/' ) ) {
     99        $block_name = 'core/' . $block_name;
     100    }
     101
     102    // Test for existence of block by its fully qualified name.
     103    $has_block = false !== strpos( $post, '<!-- wp:' . $block_name . ' ' );
     104
     105    if ( ! $has_block ) {
     106        /*
     107         * If the given block name would serialize to a different name, test for
     108         * existence by the serialized form.
     109         */
     110        $serialized_block_name = strip_core_block_namespace( $block_name );
     111        if ( $serialized_block_name !== $block_name ) {
     112            $has_block = false !== strpos( $post, '<!-- wp:' . $serialized_block_name . ' ' );
     113        }
     114    }
     115
     116    return $has_block;
    94117}
    95118
     
    112135
    113136    return $dynamic_block_names;
     137}
     138
     139/**
     140 * Given an array of attributes, returns a string in the serialized attributes
     141 * format prepared for post content.
     142 *
     143 * The serialized result is a JSON-encoded string, with unicode escape sequence
     144 * substitution for characters which might otherwise interfere with embedding
     145 * the result in an HTML comment.
     146 *
     147 * @since 5.3.1
     148 *
     149 * @param array $attributes Attributes object.
     150 * @return string Serialized attributes.
     151 */
     152function serialize_block_attributes( $block_attributes ) {
     153    $encoded_attributes = json_encode( $block_attributes );
     154    $encoded_attributes = preg_replace( '/--/', '\\u002d\\u002d', $encoded_attributes );
     155    $encoded_attributes = preg_replace( '/</', '\\u003c', $encoded_attributes );
     156    $encoded_attributes = preg_replace( '/>/', '\\u003e', $encoded_attributes );
     157    $encoded_attributes = preg_replace( '/&/', '\\u0026', $encoded_attributes );
     158    // Regex: /\\"/
     159    $encoded_attributes = preg_replace( '/\\\\"/', '\\u0022', $encoded_attributes );
     160
     161    return $encoded_attributes;
     162}
     163
     164/**
     165 * Returns the block name to use for serialization. This will remove the default
     166 * "core/" namespace from a block name.
     167 *
     168 * @since 5.3.1
     169 *
     170 * @param string $block_name Original block name.
     171 * @return string Block name to use for serialization.
     172 */
     173function strip_core_block_namespace( $block_name = null ) {
     174    if ( is_string( $block_name ) && 0 === strpos( $block_name, 'core/' ) ) {
     175        return substr( $block_name, 5 );
     176    }
     177
     178    return $block_name;
     179}
     180
     181/**
     182 * Returns the content of a block, including comment delimiters.
     183 *
     184 * @since 5.3.1
     185 *
     186 * @param string $block_name Block name.
     187 * @param array  $attributes Block attributes.
     188 * @param string $content    Block save content.
     189 * @return string Comment-delimited block content.
     190 */
     191function get_comment_delimited_block_content( $block_name = null, $block_attributes, $block_content ) {
     192    if ( is_null( $block_name ) ) {
     193        return $block_content;
     194    }
     195
     196    $serialized_block_name = strip_core_block_namespace( $block_name );
     197    $serialized_attributes = empty( $block_attributes ) ? '' : serialize_block_attributes( $block_attributes ) . ' ';
     198
     199    if ( empty( $block_content ) ) {
     200        return sprintf( '<!-- wp:%s %s/-->', $serialized_block_name, $serialized_attributes );
     201    }
     202
     203    return sprintf(
     204        '<!-- wp:%s %s-->%s<!-- /wp:%s -->',
     205        $serialized_block_name,
     206        $serialized_attributes,
     207        $block_content,
     208        $serialized_block_name
     209    );
     210}
     211
     212/**
     213 * Returns the content of a block, including comment delimiters, serializing all
     214 * attributes from the given parsed block.
     215 *
     216 * This should be used when preparing a block to be saved to post content.
     217 * Prefer `render_block` when preparing a block for display. Unlike
     218 * `render_block`, this does not evaluate a block's `render_callback`, and will
     219 * instead preserve the markup as parsed.
     220 *
     221 * @since 5.3.1
     222 *
     223 * @param WP_Block_Parser_Block $block A single parsed block object.
     224 * @return string String of rendered HTML.
     225 */
     226function serialize_block( $block ) {
     227    $block_content = '';
     228
     229    $index = 0;
     230    foreach ( $block['innerContent'] as $chunk ) {
     231        $block_content .= is_string( $chunk ) ? $chunk : serialize_block( $block['innerBlocks'][ $index++ ] );
     232    }
     233
     234    if ( ! is_array( $block['attrs'] ) ) {
     235        $block['attrs'] = array();
     236    }
     237
     238    return get_comment_delimited_block_content(
     239        $block['blockName'],
     240        $block['attrs'],
     241        $block_content
     242    );
     243}
     244
     245/**
     246 * Returns a joined string of the aggregate serialization of the given parsed
     247 * blocks.
     248 *
     249 * @since 5.3.1
     250 *
     251 * @param WP_Block_Parser_Block[] $blocks Parsed block objects.
     252 * @return string String of rendered HTML.
     253 */
     254function serialize_blocks( $blocks ) {
     255    return implode( '', array_map( 'serialize_block', $blocks ) );
     256}
     257
     258/**
     259 * Filters and sanitizes block content to remove non-allowable HTML from
     260 * parsed block attribute values.
     261 *
     262 * @since 5.3.1
     263 *
     264 * @param string         $text              Text that may contain block content.
     265 * @param array[]|string $allowed_html      An array of allowed HTML elements
     266 *                                          and attributes, or a context name
     267 *                                          such as 'post'.
     268 * @param string[]       $allowed_protocols Array of allowed URL protocols.
     269 * @return string The filtered and sanitized content result.
     270 */
     271function filter_block_content( $text, $allowed_html = 'post', $allowed_protocols = array() ) {
     272    $result = '';
     273
     274    $blocks = parse_blocks( $text );
     275    foreach ( $blocks as $block ) {
     276        $block   = filter_block_kses( $block, $allowed_html, $allowed_protocols );
     277        $result .= serialize_block( $block );
     278    }
     279
     280    return $result;
     281}
     282
     283/**
     284 * Filters and sanitizes a parsed block to remove non-allowable HTML from block
     285 * attribute values.
     286 *
     287 * @since 5.3.1
     288 *
     289 * @param WP_Block_Parser_Block $block             The parsed block object.
     290 * @param array[]|string        $allowed_html      An array of allowed HTML
     291 *                                                 elements and attributes, or a
     292 *                                                 context name such as 'post'.
     293 * @param string[]              $allowed_protocols Allowed URL protocols.
     294 * @return array The filtered and sanitized block object result.
     295 */
     296function filter_block_kses( $block, $allowed_html, $allowed_protocols = array() ) {
     297    $block['attrs'] = filter_block_kses_value( $block['attrs'], $allowed_html, $allowed_protocols );
     298
     299    if ( is_array( $block['innerBlocks'] ) ) {
     300        foreach ( $block['innerBlocks'] as $i => $inner_block ) {
     301            $block['innerBlocks'][ $i ] = filter_block_kses( $inner_block, $allowed_html, $allowed_protocols );
     302        }
     303    }
     304
     305    return $block;
     306}
     307
     308/**
     309 * Filters and sanitizes a parsed block attribute value to remove non-allowable
     310 * HTML.
     311 *
     312 * @since 5.3.1
     313 *
     314 * @param mixed          $value             The attribute value to filter.
     315 * @param array[]|string $allowed_html      An array of allowed HTML elements
     316 *                                          and attributes, or a context name
     317 *                                          such as 'post'.
     318 * @param string[]       $allowed_protocols Array of allowed URL protocols.
     319 * @return array The filtered and sanitized result.
     320 */
     321function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = array() ) {
     322    if ( is_array( $value ) ) {
     323        foreach ( $value as $key => $inner_value ) {
     324            $filtered_key   = filter_block_kses_value( $key, $allowed_html, $allowed_protocols );
     325            $filtered_value = filter_block_kses_value( $inner_value, $allowed_html, $allowed_protocols );
     326
     327            if ( $filtered_key !== $key ) {
     328                unset( $value[ $key ] );
     329            }
     330
     331            $value[ $filtered_key ] = $filtered_value;
     332        }
     333    } elseif ( is_string( $value ) ) {
     334        return wp_kses( $value, $allowed_html, $allowed_protocols );
     335    }
     336
     337    return $value;
    114338}
    115339
  • branches/5.1/src/wp-includes/default-filters.php

    r44714 r46907  
    246246add_filter( 'teeny_mce_before_init', '_mce_set_direction' );
    247247add_filter( 'pre_kses', 'wp_pre_kses_less_than' );
     248add_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10, 3 );
    248249add_filter( 'sanitize_title', 'sanitize_title_with_dashes', 10, 3 );
    249250add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
  • branches/5.1/src/wp-includes/formatting.php

    r45992 r46907  
    30423042function wp_targeted_link_rel( $text ) {
    30433043    // Don't run (more expensive) regex if no links with targets.
    3044     if ( stripos( $text, 'target' ) !== false && stripos( $text, '<a ' ) !== false ) {
    3045         $text = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $text );
     3044    if ( stripos( $text, 'target' ) === false || stripos( $text, '<a ' ) === false || is_serialized( $text ) ) {
     3045        return $text;
     3046    }
     3047
     3048    $script_and_style_regex = '/<(script|style).*?<\/\\1>/si';
     3049
     3050    preg_match_all( $script_and_style_regex, $text, $matches );
     3051    $extra_parts = $matches[0];
     3052    $html_parts  = preg_split( $script_and_style_regex, $text );
     3053
     3054    foreach ( $html_parts as &$part ) {
     3055        $part = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $part );
     3056    }
     3057
     3058    $text = '';
     3059    for ( $i = 0; $i < count( $html_parts ); $i++ ) {
     3060        $text .= $html_parts[ $i ];
     3061        if ( isset( $extra_parts[ $i ] ) ) {
     3062            $text .= $extra_parts[ $i ];
     3063        }
    30463064    }
    30473065
     
    30593077 */
    30603078function wp_targeted_link_rel_callback( $matches ) {
    3061     $link_html = $matches[1];
    3062     $rel_match = array();
     3079    $link_html          = $matches[1];
     3080    $original_link_html = $link_html;
     3081
     3082    // Consider the html escaped if there are no unescaped quotes
     3083    $is_escaped = ! preg_match( '/(^|[^\\\\])[\'"]/', $link_html );
     3084    if ( $is_escaped ) {
     3085        // Replace only the quotes so that they are parsable by wp_kses_hair, leave the rest as is
     3086        $link_html = preg_replace( '/\\\\([\'"])/', '$1', $link_html );
     3087    }
     3088
     3089    $atts = wp_kses_hair( $link_html, wp_allowed_protocols() );
    30633090
    30643091    /**
     
    30723099    $rel = apply_filters( 'wp_targeted_link_rel', 'noopener noreferrer', $link_html );
    30733100
    3074     // Avoid additional regex if the filter removes rel values.
    3075     if ( ! $rel ) {
    3076         return "<a $link_html>";
    3077     }
    3078 
    3079     // Value with delimiters, spaces around are optional.
    3080     $attr_regex = '|rel\s*=\s*?(\\\\{0,1}["\'])(.*?)\\1|i';
    3081     preg_match( $attr_regex, $link_html, $rel_match );
    3082 
    3083     if ( empty( $rel_match[0] ) ) {
    3084         // No delimiters, try with a single value and spaces, because `rel =  va"lue` is totally fine...
    3085         $attr_regex = '|rel\s*=(\s*)([^\s]*)|i';
    3086         preg_match( $attr_regex, $link_html, $rel_match );
    3087     }
    3088 
    3089     if ( ! empty( $rel_match[0] ) ) {
    3090         $parts     = preg_split( '|\s+|', strtolower( $rel_match[2] ) );
    3091         $parts     = array_map( 'esc_attr', $parts );
    3092         $needed    = explode( ' ', $rel );
    3093         $parts     = array_unique( array_merge( $parts, $needed ) );
    3094         $delimiter = trim( $rel_match[1] ) ? $rel_match[1] : '"';
    3095         $rel       = 'rel=' . $delimiter . trim( implode( ' ', $parts ) ) . $delimiter;
    3096         $link_html = str_replace( $rel_match[0], $rel, $link_html );
    3097     } else {
    3098         $link_html .= " rel=\"$rel\"";
     3101    // Return early if no rel values to be added or if no actual target attribute
     3102    if ( ! $rel || ! isset( $atts['target'] ) ) {
     3103        return "<a $original_link_html>";
     3104    }
     3105
     3106    if ( isset( $atts['rel'] ) ) {
     3107        $all_parts = preg_split( '/\s/', "{$atts['rel']['value']} $rel", -1, PREG_SPLIT_NO_EMPTY );
     3108        $rel       = implode( ' ', array_unique( $all_parts ) );
     3109    }
     3110
     3111    $atts['rel']['whole'] = 'rel="' . esc_attr( $rel ) . '"';
     3112    $link_html            = join( ' ', array_column( $atts, 'whole' ) );
     3113
     3114    if ( $is_escaped ) {
     3115        $link_html = preg_replace( '/[\'"]/', '\\\\$0', $link_html );
    30993116    }
    31003117
     
    47984815
    47994816/**
     4817 * Remove non-allowable HTML from parsed block attribute values when filtering
     4818 * in the post context.
     4819 *
     4820 * @since 5.3.1
     4821 *
     4822 * @param string         $string            Content to be run through KSES.
     4823 * @param array[]|string $allowed_html      An array of allowed HTML elements
     4824 *                                          and attributes, or a context name
     4825 *                                          such as 'post'.
     4826 * @param string[]       $allowed_protocols Array of allowed URL protocols.
     4827 * @return string Filtered text to run through KSES.
     4828 */
     4829function wp_pre_kses_block_attributes( $string, $allowed_html, $allowed_protocols ) {
     4830    /*
     4831     * `filter_block_content` is expected to call `wp_kses`. Temporarily remove
     4832     * the filter to avoid recursion.
     4833     */
     4834    remove_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10 );
     4835    $string = filter_block_content( $string, $allowed_html, $allowed_protocols );
     4836    add_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10, 3 );
     4837
     4838    return $string;
     4839}
     4840
     4841/**
    48004842 * WordPress implementation of PHP sprintf() with filters.
    48014843 *
  • branches/5.1/src/wp-includes/kses.php

    r46002 r46907  
    16591659function wp_kses_bad_protocol_once( $string, $allowed_protocols, $count = 1 ) {
    16601660    $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 );
     1661    $string2 = preg_split( '/:|&#0*58;|&#x0*3a;|&colon;/i', $string, 2 );
    16621662    if ( isset( $string2[1] ) && ! preg_match( '%/\?%', $string2[0] ) ) {
    16631663        $string   = trim( $string2[1] );
  • branches/5.1/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php

    r44225 r46907  
    500500        }
    501501
    502         if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
     502        if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
    503503            return new WP_Error( 'rest_cannot_assign_sticky', __( 'Sorry, you are not allowed to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) );
    504504        }
     
    655655        }
    656656
    657         if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
     657        if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
    658658            return new WP_Error( 'rest_cannot_assign_sticky', __( 'Sorry, you are not allowed to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) );
    659659        }
     
    957957     */
    958958    protected function prepare_item_for_database( $request ) {
    959         $prepared_post = new stdClass;
     959        $prepared_post = new stdClass();
    960960
    961961        // Post ID.
  • branches/5.1/tests/phpunit/tests/blocks/block-type.php

    r44269 r46907  
    303303        // even if it detects a proper $post global it should still be false for a missing block.
    304304        $this->assertFalse( has_block( 'core/fake' ) );
     305    }
     306
     307    public function test_post_has_block_serialized_name() {
     308        $content = '<!-- wp:serialized /--><!-- wp:core/normalized /--><!-- wp:plugin/third-party /-->';
     309
     310        $this->assertTrue( has_block( 'core/serialized', $content ) );
     311
     312        /*
     313         * Technically, `has_block` should receive a "full" (normalized, parsed)
     314         * block name. But this test conforms to expected pre-5.3.1 behavior.
     315         */
     316        $this->assertTrue( has_block( 'serialized', $content ) );
     317        $this->assertTrue( has_block( 'core/normalized', $content ) );
     318        $this->assertTrue( has_block( 'normalized', $content ) );
     319        $this->assertFalse( has_block( 'plugin/normalized', $content ) );
     320        $this->assertFalse( has_block( 'plugin/serialized', $content ) );
     321        $this->assertFalse( has_block( 'third-party', $content ) );
     322        $this->assertFalse( has_block( 'core/third-party', $content ) );
    305323    }
    306324
  • branches/5.1/tests/phpunit/tests/formatting/WPTargetedLinkRel.php

    r44714 r46907  
    3939    public function test_rel_with_single_quote_delimiter() {
    4040        $content  = '<p>Links: <a href="/" rel=\'existing values\' target="_blank">Existing rel</a></p>';
    41         $expected = '<p>Links: <a href="/" rel=\'existing values noopener noreferrer\' target="_blank">Existing rel</a></p>';
     41        $expected = '<p>Links: <a href="/" rel="existing values noopener noreferrer" target="_blank">Existing rel</a></p>';
    4242        $this->assertEquals( $expected, wp_targeted_link_rel( $content ) );
    4343    }
     
    5252        $content  = '<p>Links: <a href="/" rel = existing target="_blank">Existing rel</a></p>';
    5353        $expected = '<p>Links: <a href="/" rel="existing noopener noreferrer" target="_blank">Existing rel</a></p>';
    54         $this->assertEquals( $expected, wp_targeted_link_rel( $content ) );
    55     }
    56 
    57     public function test_rel_value_spaced_and_no_delimiter_and_values_to_escape() {
    58         $content  = '<p>Links: <a href="/" rel = existing"value target="_blank">Existing rel</a></p>';
    59         $expected = '<p>Links: <a href="/" rel="existing&quot;value noopener noreferrer" target="_blank">Existing rel</a></p>';
    6054        $this->assertEquals( $expected, wp_targeted_link_rel( $content ) );
    6155    }
     
    10296        $this->assertEquals( $expected, $post->post_content );
    10397    }
     98
     99    /**
     100     * Ensure JSON format is preserved when relation attribute (rel) is missing.
     101     *
     102     * @ticket 46316
     103     */
     104    public function test_wp_targeted_link_rel_should_preserve_json() {
     105        $content  = '<p>Links: <a href=\"\/\" target=\"_blank\">No rel<\/a><\/p>';
     106        $expected = '<p>Links: <a href=\"\/\" target=\"_blank\" rel=\"noopener noreferrer\">No rel<\/a><\/p>';
     107        $this->assertEquals( $expected, wp_targeted_link_rel( $content ) );
     108    }
     109
     110    /**
     111     * Ensure the content of style and script tags are not processed
     112     *
     113     * @ticket 47244
     114     */
     115    public function test_wp_targeted_link_rel_skips_style_and_scripts() {
     116        $content  = '<style><a href="/" target=a></style><p>Links: <script>console.log("<a href=\'/\' target=a>hi</a>");</script><script>alert(1);</script>here <a href="/" target=_blank>aq</a></p><script>console.log("<a href=\'last\' target=\'_blank\'")</script>';
     117        $expected = '<style><a href="/" target=a></style><p>Links: <script>console.log("<a href=\'/\' target=a>hi</a>");</script><script>alert(1);</script>here <a href="/" target="_blank" rel="noopener noreferrer">aq</a></p><script>console.log("<a href=\'last\' target=\'_blank\'")</script>';
     118        $this->assertEquals( $expected, wp_targeted_link_rel( $content ) );
     119    }
     120
     121    /**
     122     * Ensure entirely serialized content is ignored.
     123     *
     124     * @ticket 46402
     125     */
     126    public function test_ignore_entirely_serialized_content() {
     127        $content  = 'a:1:{s:4:"html";s:52:"<p>Links: <a href="/" target="_blank">No Rel</a></p>";}';
     128        $expected = 'a:1:{s:4:"html";s:52:"<p>Links: <a href="/" target="_blank">No Rel</a></p>";}';
     129        $this->assertEquals( $expected, wp_targeted_link_rel( $content ) );
     130    }
     131
     132    public function test_wp_targeted_link_rel_tab_separated_values_are_split() {
     133        $content  = "<p>Links: <a href=\"/\" target=\"_blank\" rel=\"ugc\t\tnoopener\t\">No rel</a></p>";
     134        $expected = '<p>Links: <a href="/" target="_blank" rel="ugc noopener noreferrer">No rel</a></p>';
     135        $this->assertEquals( $expected, wp_targeted_link_rel( $content ) );
     136    }
     137
    104138}
  • branches/5.1/tests/phpunit/tests/kses.php

    r46002 r46907  
    179179        }
    180180
     181        $bad_not_normalized = array(
     182            'dummy&colon;alert(1)',
     183            'javascript&colon;alert(1)',
     184            'javascript&CoLon;alert(1)',
     185            'javascript&COLON;alert(1);',
     186            'javascript&#58;alert(1);',
     187            'javascript&#0058;alert(1);',
     188            'javascript&#0000058alert(1);',
     189            'jav    ascript&COLON;alert(1);',
     190            'javascript&#58;javascript&colon;alert(1);',
     191            'javascript&#58;javascript&colon;alert(1);',
     192            'javascript&#0000058javascript&colon;alert(1);',
     193            'javascript&#58;javascript&#0000058alert(1);',
     194            'javascript&#58alert(1)',
     195        );
     196        foreach ( $bad_not_normalized as $k => $x ) {
     197            $result = wp_kses_bad_protocol( $x, wp_allowed_protocols() );
     198            if ( ! empty( $result ) && 'alert(1);' !== $result && 'alert(1)' !== $result ) {
     199                $this->fail( "wp_kses_bad_protocol failed on $k, $x. Result: $result" );
     200            }
     201        }
     202
    181203        $safe = array(
    182204            'dummy:alert(1)',
Note: See TracChangeset for help on using the changeset viewer.