Make WordPress Core

Ticket #35022: 35022.4.patch

File 35022.4.patch, 12.4 KB (added by gitlost, 8 years ago)

Refactoring to deal with seg faults in old versions of PCRE when using grouped expressions.

  • src/wp-includes/formatting.php

     
    2525 * @since 0.71
    2626 *
    2727 * @global array $wp_cockneyreplace Array of formatted entities for certain common phrases
    28  * @global array $shortcode_tags
    2928 * @staticvar array $static_characters
    3029 * @staticvar array $static_replacements
    3130 * @staticvar array $dynamic_characters
     
    3938 * @return string The string replaced with html entities
    4039 */
    4140function wptexturize( $text, $reset = false ) {
    42         global $wp_cockneyreplace, $shortcode_tags;
     41        global $wp_cockneyreplace;
    4342        static $static_characters = null,
    4443                $static_replacements = null,
    4544                $dynamic_characters = null,
     
    216215
    217216        // Look for shortcodes and HTML elements.
    218217
    219         preg_match_all( '@\[/?([^<>&/\[\]\x00-\x20=]++)@', $text, $matches );
    220         $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );
     218        $tagnames = get_shortcode_tagnames( $text );
    221219        $found_shortcodes = ! empty( $tagnames );
    222220        $shortcode_regex = $found_shortcodes ? _get_wptexturize_shortcode_regex( $tagnames ) : '';
    223221        $regex = _get_wptexturize_split_regex( $shortcode_regex );
     
    696694 */
    697695function _get_wptexturize_shortcode_regex( $tagnames ) {
    698696        $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
    699         $tagregexp = "(?:$tagregexp)(?=[\\s\\]\\/])"; // Excerpt of get_shortcode_regex().
     697        $tagregexp = "(?:$tagregexp)(?=" . shortcode_name_terminators() . ")"; // Excerpt of get_shortcode_regex().
    700698        $regex =
    701699                  '\['              // Find start of shortcode.
    702700                . '[\/\[]?'         // Shortcodes may begin with [/ or [[
     
    782780 *
    783781 * @since 2.9.0
    784782 *
    785  * @global array $shortcode_tags
    786  *
    787783 * @param string $pee The content.
    788784 * @return string The filtered content.
    789785 */
    790786function shortcode_unautop( $pee ) {
    791         global $shortcode_tags;
    792787
    793         if ( empty( $shortcode_tags ) || !is_array( $shortcode_tags ) ) {
     788        $tagnames = get_shortcode_tagnames( $pee );
     789        if ( empty( $tagnames ) ) {
    794790                return $pee;
    795791        }
    796792
    797         $tagregexp = join( '|', array_map( 'preg_quote', array_keys( $shortcode_tags ) ) );
     793        $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
    798794        $spaces = wp_spaces_regexp();
    799795
    800796        $pattern =
     
    804800                . '('                                // 1: The shortcode
    805801                .     '\\['                          // Opening bracket
    806802                .     "($tagregexp)"                 // 2: Shortcode name
    807                 .     '(?![\\w-])'                   // Not followed by word character or hyphen
     803                .     '(?=' . shortcode_name_terminators() . ')' // Followed by a shortcode name terminator.
    808804                                                     // Unroll the loop: Inside the opening shortcode tag
    809805                .     '[^\\]\\/]*'                   // Not a closing bracket or forward slash
    810806                .     '(?:'
  • src/wp-includes/js/shortcode.js

     
    102102                // 6. The closing tag.
    103103                // 7. An extra `]` to allow for escaping shortcodes with double `[[]]`
    104104                regexp: _.memoize( function( tag ) {
    105                         return new RegExp( '\\[(\\[?)(' + tag + ')(?![\\w-])([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' );
     105                        var shortcode_name_terminators = '[<>&\\/\\[\\]\x00-\x20=\u00a0]';
     106                        return new RegExp( '\\[(\\[?)(' + tag + ')(?=' + shortcode_name_terminators + ')([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' );
    106107                }),
    107108
    108109
  • src/wp-includes/shortcodes.php

     
    9595                return;
    9696        }
    9797
    98         if ( 0 !== preg_match( '@[<>&/\[\]\x00-\x20=]@', $tag ) ) {
     98        if ( 0 !== preg_match( '@' . shortcode_name_terminators() . '@', $tag ) ) {
    9999                /* translators: 1: shortcode name, 2: space separated list of reserved characters */
    100100                $message = sprintf( __( 'Invalid shortcode name: %1$s. Do not use spaces or reserved characters: %2$s' ), $tag, '& / < > [ ] =' );
    101101                _doing_it_wrong( __FUNCTION__, $message, '4.4.0' );
     
    157157 *
    158158 * @since 3.6.0
    159159 *
    160  * @global array $shortcode_tags
    161  *
    162160 * @param string $content Content to search for shortcodes.
    163161 * @param string $tag     Shortcode tag to check.
    164162 * @return bool Whether the passed content contains the given shortcode.
     
    193191 *
    194192 * @since 2.5.0
    195193 *
    196  * @global array $shortcode_tags List of shortcode tags and their callback hooks.
    197  *
    198194 * @param string $content Content to search for shortcodes.
    199195 * @param bool $ignore_html When true, shortcodes inside HTML elements will be skipped.
    200196 * @return string Content with shortcodes filtered out.
    201197 */
    202198function do_shortcode( $content, $ignore_html = false ) {
    203         global $shortcode_tags;
    204199
    205         if ( false === strpos( $content, '[' ) ) {
    206                 return $content;
    207         }
    208 
    209         if (empty($shortcode_tags) || !is_array($shortcode_tags))
    210                 return $content;
    211 
    212200        // Find all registered tag names in $content.
    213         preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches );
    214         $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );
     201        $tagnames = get_shortcode_tagnames( $content );
    215202
    216203        if ( empty( $tagnames ) ) {
    217204                return $content;
     
    245232 *
    246233 * @since 2.5.0
    247234 *
    248  * @global array $shortcode_tags
    249  *
    250235 * @param array $tagnames List of shortcodes to find. Optional. Defaults to all registered shortcodes.
    251236 * @return string The shortcode search regular expression
    252237 */
    253238function get_shortcode_regex( $tagnames = null ) {
    254         global $shortcode_tags;
    255239
    256240        if ( empty( $tagnames ) ) {
    257                 $tagnames = array_keys( $shortcode_tags );
     241                $tagnames = get_shortcode_tagnames();
    258242        }
    259243        $tagregexp = join( '|', array_map('preg_quote', $tagnames) );
    260244
     
    264248                  '\\['                              // Opening bracket
    265249                . '(\\[?)'                           // 1: Optional second opening bracket for escaping shortcodes: [[tag]]
    266250                . "($tagregexp)"                     // 2: Shortcode name
    267                 . '(?![\\w-])'                       // Not followed by word character or hyphen
     251                . '(?=' . shortcode_name_terminators() . ')' // Followed by a shortcode name terminator.
    268252                . '('                                // 3: Unroll the loop: Inside the opening shortcode tag
    269253                .     '[^\\]\\/]*'                   // Not a closing bracket or forward slash
    270254                .     '(?:'
     
    292276}
    293277
    294278/**
     279 * Return regular expression of characters that terminate the name of a shortcode.
     280 * Dependent on charset of blog.
     281 *
     282 * @since 4.7.0
     283 *
     284 * @return string Regular expression of terminating characters.
     285 */
     286function shortcode_name_terminators() {
     287        // Needs to be synced with get_shortcode_tagnames() expression below.
     288        if ( 'UTF-8' === _canonical_charset( get_option( 'blog_charset' ) ) ) {
     289                return '(?:[<>&\/\[\]\x00-\x20=]|\xc2\xa0)';
     290        }
     291        return '[<>&\/\[\]\x00-\x20=]';
     292}
     293
     294/**
     295 * Return registered shortcode tagnames, reduced to those in $text if given.
     296 *
     297 * @since 4.7.0
     298 *
     299 * @global array $shortcode_tags
     300 *
     301 * @param string $text Optional. If given, only those registered shortcodes that appear in $text are returned.
     302 * @return array Array of shortcode names.
     303 */
     304function get_shortcode_tagnames( $text = null ) {
     305        global $shortcode_tags;
     306
     307        if ( ! $shortcode_tags || ! is_array( $shortcode_tags ) ) {
     308                return array();
     309        }
     310        if ( null === $text ) {
     311                return array_keys( $shortcode_tags );
     312        }
     313        if ( false === strpos( $text, '[' ) ) {
     314                return array();
     315        }
     316
     317        // Grouped expressions with unlimited repetition cause seg faults or match failures in PCRE <= 8.12 so use simple character class first and then post-process the matches.
     318        if ( ! preg_match_all( '/\[(?!\[)\/?\K[^<>&\/\[\]\x00-\x20=]+/', $text, $matches ) ) { // Needs to be synced with shortcode_name_terminators() expression above.
     319                return array();
     320        }
     321        if ( false !== strpos( $text, "\xc2\xa0" ) && 'UTF-8' === _canonical_charset( get_option( 'blog_charset' ) ) ) {
     322                $matches[0] = array_map( '_shortcode_name_terminator_chop_cb', $matches[0] );
     323        }
     324
     325        return array_values( array_intersect( array_keys( $shortcode_tags ), $matches[0] ) );
     326}
     327
     328/**
     329 * Callback for array_map in {@see get_shortcode_tagnames()} to chop off UTF-8 no-break space from potential shortcode name.
     330 *
     331 * @since 4.7.0
     332 * @access private
     333 *
     334 * @param string $entry The potential shortcode name match.
     335 * @return string The potential shortcode name chopped down if necessary.
     336 */
     337function _shortcode_name_terminator_chop_cb( $entry ) {
     338        if ( false !== ( $pos = strpos( $entry, "\xc2\xa0" ) ) ) {
     339                $entry = substr( $entry, 0, $pos );
     340        }
     341        return $entry;
     342}
     343
     344/**
    295345 * Regular Expression callable for do_shortcode() for calling shortcode hook.
    296346 * @see get_shortcode_regex for details of the match array contents.
    297347 *
     
    580630 *
    581631 * @since 2.5.0
    582632 *
    583  * @global array $shortcode_tags
    584  *
    585633 * @param string $content Content to remove shortcode tags.
    586634 * @return string Content without shortcode tags.
    587635 */
    588636function strip_shortcodes( $content ) {
    589         global $shortcode_tags;
    590637
    591         if ( false === strpos( $content, '[' ) ) {
    592                 return $content;
    593         }
    594 
    595         if (empty($shortcode_tags) || !is_array($shortcode_tags))
    596                 return $content;
    597 
    598638        // Find all registered tag names in $content.
    599         preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches );
    600         $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );
     639        $tagnames = get_shortcode_tagnames( $content );
    601640
    602641        if ( empty( $tagnames ) ) {
    603642                return $content;
  • tests/phpunit/tests/shortcode.php

     
    332332        }
    333333
    334334        /**
     335         * @ticket 35022
     336         */
     337        function test_no_break_space_following_shortcode_tag() {
     338                do_shortcode( "[test-shortcode-tag\xC2\xA0foo=\"bar\"]" );
     339                $this->assertSame( 'test-shortcode-tag', $this->tagname );
     340                $this->assertSame( array( 'foo' => 'bar' ), $this->atts );
     341        }
     342
     343        /**
     344         * @ticket 35022
     345         */
     346        function test_non_no_break_space_following_shortcode_tag() {
     347                do_shortcode( "[test-shortcode-tag\xC2\xA1foo=\"bar\"]" );
     348                $this->assertNull( $this->tagname );
     349                $this->assertNull( $this->atts );
     350        }
     351
     352        function _charset_iso_8859_1() {
     353                return 'iso-8859-1';
     354        }
     355
     356        /**
     357         * @ticket 35022
     358         */
     359        function test_no_break_space_following_shortcode_tag_iso_8859_1() {
     360                add_filter( 'pre_option_blog_charset', array( $this, '_charset_iso_8859_1' ) );
     361
     362                do_shortcode( "[test-shortcode-tag foo=\"bar\"]" );
     363                $this->assertSame( 'test-shortcode-tag', $this->tagname );
     364                $this->assertSame( array( 'foo' => 'bar' ), $this->atts );
     365
     366                $this->atts = $this->content = $this->tagname = null;
     367                do_shortcode( "[test-shortcode-tag\xA0foo=\"bar\"]" ); // ISO-8859-1 no-break space not supported currently.
     368                $this->assertNull( $this->tagname );
     369                $this->assertNull( $this->atts );
     370
     371                $this->atts = $this->content = $this->tagname = null;
     372                do_shortcode( "[test-shortcode-tag\xC2\xA0foo=\"bar\"]" );
     373                $this->assertNull( $this->tagname );
     374                $this->assertNull( $this->atts );
     375
     376                remove_filter( 'pre_option_blog_charset', array( $this, '_charset_iso_8859_1' ) );
     377        }
     378
     379        /**
    335380         * @ticket 14050
    336381         */
    337382        function test_shortcode_unautop() {
     
    608653                                '',
    609654                                false,
    610655                        ),
     656                        array(
     657                                "bad\xc2\xa0space",
     658                                false,
     659                        ),
    611660                );
    612661        }
    613662
     
    625674                                'unreserved!#$%()*+,-.;?@^_{|}~chars',
    626675                                true,
    627676                        ),
     677                        array(
     678                                "unreservedutf8\xc2\xa1\xe0\xa0\x80\xf0\xa0\x80\x80\xc2\xa2chars",
     679                                true,
     680                        ),
    628681                );
    629682        }
    630683
  • tests/qunit/wp-includes/js/shortcode.js

     
    9696                equal( result, undefined, 'stub does not trigger match' );
    9797        });
    9898
     99        test( 'next() should find the shortcode delimited by no-break space (U+00A0)', function() {
     100                var result;
     101
     102
     103                result = wp.shortcode.next( 'foo', 'this has a no-break space delimited [foo\u00a0param="foo"] shortcode' );
     104                equal( result.index, 36, 'no-break space delimited foo shortcode found at index 36' );
     105        });
     106
    99107        test( 'replace() should replace the shortcode', function() {
    100108                var result;
    101109