WordPress.org

Make WordPress Core

Changeset 34747


Ignore:
Timestamp:
10/01/2015 06:04:13 PM (4 years ago)
Author:
wonderboymusic
Message:

Shortcodes: Fix PCRE performance bugs in get_shortcode_regexp() and related to wptexturize(), do_shortcode(), and strip_shortcodes()

Alters unit tests.

Props miqrogroove.
Fixes #33517.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/formatting.php

    r34727 r34747  
    217217    // Look for shortcodes and HTML elements.
    218218
    219     $tagnames = array_keys( $shortcode_tags );
    220     $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
    221     $tagregexp = "(?:$tagregexp)(?![\\w-])"; // Excerpt of get_shortcode_regex().
     219    preg_match_all( '@\[/?([^<>&/\[\]\x00-\x20]++)@', $text, $matches );
     220    $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );
     221    $found_shortcodes = ! empty( $tagnames );
     222    if ( $found_shortcodes ) {
     223        $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
     224        $tagregexp = "(?:$tagregexp)(?![\\w-])"; // Excerpt of get_shortcode_regex().
     225        $shortcode_regex =
     226              '\['              // Find start of shortcode.
     227            . '[\/\[]?'         // Shortcodes may begin with [/ or [[
     228            . $tagregexp        // Only match registered shortcodes, because performance.
     229            . '(?:'
     230            .     '[^\[\]<>]+'  // Shortcodes do not contain other shortcodes. Quantifier critical.
     231            . '|'
     232            .     '<[^\[\]>]*>' // HTML elements permitted. Prevents matching ] before >.
     233            . ')*+'             // Possessive critical.
     234            . '\]'              // Find end of shortcode.
     235            . '\]?';            // Shortcodes may end with ]]
     236    }
    222237
    223238    $comment_regex =
     
    229244        . '(?:-->)?';   // End of comment. If not found, match all input.
    230245
    231     $shortcode_regex =
    232           '\['              // Find start of shortcode.
    233         . '[\/\[]?'         // Shortcodes may begin with [/ or [[
    234         . $tagregexp        // Only match registered shortcodes, because performance.
    235         . '(?:'
    236         .     '[^\[\]<>]+'  // Shortcodes do not contain other shortcodes. Quantifier critical.
     246    $html_regex =            // Needs replaced with wp_html_split() per Shortcode API Roadmap.
     247          '<'                // Find start of element.
     248        . '(?(?=!--)'        // Is this a comment?
     249        .     $comment_regex // Find end of comment.
    237250        . '|'
    238         .     '<[^\[\]>]*>' // HTML elements permitted. Prevents matching ] before >.
    239         . ')*+'             // Possessive critical.
    240         . '\]'              // Find end of shortcode.
    241         . '\]?';            // Shortcodes may end with ]]
    242 
    243     $regex =
    244           '/('                   // Capture the entire match.
    245         .     '<'                // Find start of element.
    246         .     '(?(?=!--)'        // Is this a comment?
    247         .         $comment_regex // Find end of comment.
    248         .     '|'
    249         .         '[^>]*>'       // Find end of element.
    250         .     ')'
    251         . '|'
    252         .     $shortcode_regex   // Find shortcodes.
    253         . ')/s';
     251        .     '[^>]*>?'      // Find end of element. If not found, match all input.
     252        . ')';
     253
     254    if ( $found_shortcodes ) {
     255        $regex = '/(' . $html_regex . '|' . $shortcode_regex . ')/s';
     256    } else {
     257        $regex = '/(' . $html_regex . ')/s';
     258    }
    254259
    255260    $textarr = preg_split( $regex, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
     
    258263        // Only call _wptexturize_pushpop_element if $curl is a delimiter.
    259264        $first = $curl[0];
    260         if ( '<' === $first && '<!--' === substr( $curl, 0, 4 ) ) {
    261             // This is an HTML comment delimiter.
    262 
    263             continue;
    264 
    265         } elseif ( '<' === $first && '>' === substr( $curl, -1 ) ) {
    266             // This is an HTML element delimiter.
    267 
    268             _wptexturize_pushpop_element( $curl, $no_texturize_tags_stack, $no_texturize_tags );
     265        if ( '<' === $first ) {
     266            if ( '<!--' === substr( $curl, 0, 4 ) ) {
     267                // This is an HTML comment delimeter.
     268                continue;
     269            } else {
     270                // This is an HTML element delimiter.
     271                _wptexturize_pushpop_element( $curl, $no_texturize_tags_stack, $no_texturize_tags );
     272            }
    269273
    270274        } elseif ( '' === trim( $curl ) ) {
    271275            // This is a newline between delimiters.  Performance improves when we check this.
    272 
    273276            continue;
    274277
    275         } elseif ( '[' === $first && 1 === preg_match( '/^' . $shortcode_regex . '$/', $curl ) ) {
     278        } elseif ( '[' === $first && $found_shortcodes && 1 === preg_match( '/^' . $shortcode_regex . '$/', $curl ) ) {
    276279            // This is a shortcode delimiter.
    277280
  • trunk/src/wp-includes/shortcodes.php

    r34745 r34747  
    209209        return $content;
    210210
    211     $tagnames = array_keys($shortcode_tags);
    212     $tagregexp = join( '|', array_map('preg_quote', $tagnames) );
    213     $pattern = "/\\[($tagregexp)/s";
    214 
    215     if ( 1 !== preg_match( $pattern, $content ) ) {
    216         // Avoids parsing HTML when there are no shortcodes or embeds anyway.
     211    // Find all registered tag names in $content.
     212    preg_match_all( '@\[([^<>&/\[\]\x00-\x20]++)@', $content, $matches );
     213    $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );
     214
     215    if ( empty( $tagnames ) ) {
    217216        return $content;
    218217    }
    219218
    220     $content = do_shortcodes_in_html_tags( $content, $ignore_html );
    221 
    222     $pattern = get_shortcode_regex();
     219    $content = do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames );
     220
     221    $pattern = get_shortcode_regex( $tagnames );
    223222    $content = preg_replace_callback( "/$pattern/s", 'do_shortcode_tag', $content );
    224223
     
    248247 * @global array $shortcode_tags
    249248 *
     249 * @param array $tagnames List of shortcodes to find. Optional. Defaults to all registered shortcodes.
    250250 * @return string The shortcode search regular expression
    251251 */
    252 function get_shortcode_regex() {
    253     global $shortcode_tags;
    254     $tagnames = array_keys($shortcode_tags);
     252function get_shortcode_regex( $tagnames = null ) {
     253    global $shortcode_tags;
     254
     255    if ( empty( $tagnames ) ) {
     256        $tagnames = array_keys( $shortcode_tags );
     257    }
    255258    $tagregexp = join( '|', array_map('preg_quote', $tagnames) );
    256259
     
    338341 * @param string $content Content to search for shortcodes
    339342 * @param bool $ignore_html When true, all square braces inside elements will be encoded.
     343 * @param array $tagnames List of shortcodes to find.
    340344 * @return string Content with shortcodes filtered out.
    341345 */
    342 function do_shortcodes_in_html_tags( $content, $ignore_html ) {
     346function do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames ) {
    343347    // Normalize entities in unfiltered HTML before adding placeholders.
    344348    $trans = array( '&#91;' => '&#091;', '&#93;' => '&#093;' );
     
    346350    $trans = array( '[' => '&#91;', ']' => '&#93;' );
    347351
    348     $pattern = get_shortcode_regex();
     352    $pattern = get_shortcode_regex( $tagnames );
    349353    $textarr = wp_html_split( $content );
    350354
     
    558562        return $content;
    559563
    560     $content = do_shortcodes_in_html_tags( $content, true );
    561 
    562     $pattern = get_shortcode_regex();
     564    // Find all registered tag names in $content.
     565    preg_match_all( '@\[([^<>&/\[\]\x00-\x20]++)@', $content, $matches );
     566    $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );
     567
     568    if ( empty( $tagnames ) ) {
     569        return $content;
     570    }
     571
     572    $content = do_shortcodes_in_html_tags( $content, true, $tagnames );
     573
     574    $pattern = get_shortcode_regex( $tagnames );
    563575    $content = preg_replace_callback( "/$pattern/s", 'strip_shortcode_tag', $content );
    564576
  • trunk/tests/phpunit/tests/formatting/WPTexturize.php

    r32863 r34747  
    375375            ),
    376376            array(
    377                 "word <'word word", // Invalid HTML input triggers the apos in a word pattern.
    378                 "word <&#8217;word word",
     377                "word <'word word", // Invalid HTML
     378                "word <'word word",
    379379            ),
    380380            array(
     
    404404            array(
    405405                "word<'word word",
    406                 "word<&#8217;word word",
     406                "word<'word word",
    407407            ),
    408408            array(
     
    432432            array(
    433433                "word <' word word",
    434                 "word <&#8217; word word",
     434                "word <' word word",
    435435            ),
    436436            array(
     
    460460            array(
    461461                "word<' word word",
    462                 "word<&#8217; word word",
     462                "word<' word word",
    463463            ),
    464464            array(
     
    611611            ),
    612612            array(
    613                 'word <"word word', // Invalid HTML input triggers the closing quote pattern.
    614                 'word <&#8221;word word',
     613                'word <"word word', // Invalid HTML
     614                'word <"word word',
    615615            ),
    616616            array(
     
    644644            array(
    645645                'word<"word word',
    646                 'word<&#8221;word word',
     646                'word<"word word',
    647647            ),
    648648            array(
     
    13131313            array(
    13141314                '<br [gallery ...] ... /',
    1315                 '<br [gallery ...] &#8230; /',
     1315                '<br [gallery ...] ... /',
    13161316            ),
    13171317            array(
     
    13531353            array(
    13541354                '<br [[gallery ...]] ... /',
    1355                 '<br [[gallery ...]] &#8230; /',
     1355                '<br [[gallery ...]] ... /',
    13561356            ),
    13571357            array(
Note: See TracChangeset for help on using the changeset viewer.