Make WordPress Core

Changeset 51984


Ignore:
Timestamp:
11/02/2021 06:46:36 PM (2 years ago)
Author:
johnjamesjacoby
Message:

Permalinks: Sanitize non-visible characters inside sanitize_title_with_dashes().

This change prevents non-visible characters in titles from creating encoded values in permalinks, opting instead for the following replacement strategy:

  • Non-visible non-zero-width characters are replaced with hyphens
  • Non-visible zero-width characters are removed entirely

Included with this change are 64 additional PHPUnit assertions to confirm that only the targeted non-visible characters are sanitized as intended.

Before this change, URLs would unintentionally contain encoded values where these non-visible characters were. After this change, URLs intentionally strip out or hyphenate these non-visible characters.

Props costdev, dhanendran, hellofromtonya, paaljoachim, peterwilsoncc, poena, sergeybiryukov.

Fixes #47912.

Location:
trunk
Files:
2 edited

Legend:

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

    r51955 r51984  
    22892289                '%cc%84',
    22902290                '%cc%8c',
     2291                // Non-visible characters that display without a width.
     2292                '%e2%80%8b',
     2293                '%e2%80%8c',
     2294                '%e2%80%8d',
     2295                '%e2%80%8e',
     2296                '%e2%80%8f',
     2297                '%e2%80%aa',
     2298                '%e2%80%ab',
     2299                '%e2%80%ac',
     2300                '%e2%80%ad',
     2301                '%e2%80%ae',
     2302                '%ef%bb%bf',
    22912303            ),
    22922304            '',
     2305            $title
     2306        );
     2307
     2308        // Convert non-visible characters that display with a width to hyphen.
     2309        $title = str_replace(
     2310            array(
     2311                '%e2%80%80',
     2312                '%e2%80%81',
     2313                '%e2%80%82',
     2314                '%e2%80%83',
     2315                '%e2%80%84',
     2316                '%e2%80%85',
     2317                '%e2%80%86',
     2318                '%e2%80%87',
     2319                '%e2%80%88',
     2320                '%e2%80%89',
     2321                '%e2%80%8a',
     2322                '%e2%80%a8',
     2323                '%e2%80%a9',
     2324                '%e2%80%af',
     2325            ),
     2326            '-',
    22932327            $title
    22942328        );
  • trunk/tests/phpunit/tests/formatting/sanitizeTitleWithDashes.php

    r51623 r51984  
    148148    }
    149149
     150    /**
     151     * @ticket 47912
     152     * @dataProvider data_removes_non_visible_characters_without_width
     153     *
     154     * @param string $title     The title to be sanitized.
     155     * @param string $expected  Expected sanitized title.
     156     */
     157    public function test_removes_non_visible_characters_without_width( $title, $expected = '' ) {
     158        $this->assertSame( $expected, sanitize_title_with_dashes( $title, '', 'save' ) );
     159    }
     160
     161    /**
     162     * Data provider.
     163     *
     164     * @return array
     165     */
     166    public function data_removes_non_visible_characters_without_width() {
     167        return array(
     168            // Only the non-visible characters.
     169            'only %e2%80%8b'     => array( '%e2%80%8b' ),
     170            'only %e2%80%8c'     => array( '%e2%80%8c' ),
     171            'only %e2%80%8d'     => array( '%e2%80%8d' ),
     172            'only %e2%80%8e'     => array( '%e2%80%8e' ),
     173            'only %e2%80%8f'     => array( '%e2%80%8f' ),
     174            'only %e2%80%aa'     => array( '%e2%80%aa' ),
     175            'only %e2%80%ab'     => array( '%e2%80%ab' ),
     176            'only %e2%80%ac'     => array( '%e2%80%ac' ),
     177            'only %e2%80%ad'     => array( '%e2%80%ad' ),
     178            'only %e2%80%ae'     => array( '%e2%80%ae' ),
     179            'only %ef%bb%bf'     => array( '%ef%bb%bf' ),
     180
     181            // Non-visible characters within the title.
     182            'in middle of title' => array(
     183                'title'    => 'Nonvisible %ef%bb%bfin middle of title',
     184                'expected' => 'nonvisible-in-middle-of-title',
     185            ),
     186            'at start of title'  => array(
     187                'title'    => '%e2%80%8bNonvisible at start of title',
     188                'expected' => 'nonvisible-at-start-of-title',
     189            ),
     190            'at end of title'    => array(
     191                'title'    => 'Nonvisible at end of title %e2%80%8b',
     192                'expected' => 'nonvisible-at-end-of-title',
     193            ),
     194            'randomly in title'  => array(
     195                'title'    => 'Nonvisible%ef%bb%bf %e2%80%aerandomly %e2%80%8ein the %e2%80%8e title%e2%80%8e',
     196                'expected' => 'nonvisible-randomly-in-the-title',
     197            ),
     198        );
     199    }
     200
     201    /**
     202     * @ticket 47912
     203     * @dataProvider data_non_visible_characters_without_width_when_not_save
     204     *
     205     * @param string $title     The title to be sanitized.
     206     * @param string $expected  Expected sanitized title.
     207     */
     208    public function test_non_visible_characters_without_width_when_not_save( $title, $expected = '' ) {
     209        $this->assertSame( $expected, sanitize_title_with_dashes( $title ) );
     210    }
     211
     212    /**
     213     * Data provider.
     214     *
     215     * @return array
     216     */
     217    public function data_non_visible_characters_without_width_when_not_save() {
     218        return array(
     219            // Just the non-visible characters.
     220            'only %e2%80%8b'     => array( '%e2%80%8b', '%e2%80%8b' ),
     221            'only %e2%80%8c'     => array( '%e2%80%8c', '%e2%80%8c' ),
     222            'only %e2%80%8d'     => array( '%e2%80%8d', '%e2%80%8d' ),
     223            'only %e2%80%8e'     => array( '%e2%80%8e', '%e2%80%8e' ),
     224            'only %e2%80%8f'     => array( '%e2%80%8f', '%e2%80%8f' ),
     225            'only %e2%80%aa'     => array( '%e2%80%aa', '%e2%80%aa' ),
     226            'only %e2%80%ab'     => array( '%e2%80%ab', '%e2%80%ab' ),
     227            'only %e2%80%ac'     => array( '%e2%80%ac', '%e2%80%ac' ),
     228            'only %e2%80%ad'     => array( '%e2%80%ad', '%e2%80%ad' ),
     229            'only %e2%80%ae'     => array( '%e2%80%ae', '%e2%80%ae' ),
     230            'only %ef%bb%bf'     => array( '%ef%bb%bf', '%ef%bb%bf' ),
     231
     232            // Non-visible characters within the title.
     233            'in middle of title' => array(
     234                'title'    => 'Nonvisible %ef%bb%bfin middle of title',
     235                'expected' => 'nonvisible-%ef%bb%bfin-middle-of-title',
     236            ),
     237            'at start of title'  => array(
     238                'title'    => '%e2%80%8bNonvisible at start of title',
     239                'expected' => '%e2%80%8bnonvisible-at-start-of-title',
     240            ),
     241            'at end of title'    => array(
     242                'title'    => 'Nonvisible at end of title %e2%80%8b',
     243                'expected' => 'nonvisible-at-end-of-title-%e2%80%8b',
     244            ),
     245            'randomly in title'  => array(
     246                'title'    => 'Nonvisible%ef%bb%bf %e2%80%aerandomly %e2%80%8ein the %e2%80%8e title%e2%80%8e',
     247                'expected' => 'nonvisible%ef%bb%bf-%e2%80%aerandomly-%e2%80%8ein-the-%e2%80%8e-title%e2%80%8e',
     248            ),
     249        );
     250    }
     251
     252    /**
     253     * @ticket 47912
     254     * @dataProvider data_converts_non_visible_characters_with_width_to_hyphen
     255     *
     256     * @param string $title     The title to be sanitized.
     257     * @param string $expected  Expected sanitized title.
     258     */
     259    public function test_converts_non_visible_characters_with_width_to_hyphen( $title, $expected = '' ) {
     260        $this->assertSame( $expected, sanitize_title_with_dashes( $title, '', 'save' ) );
     261    }
     262
     263    /**
     264     * Data provider.
     265     *
     266     * @return array
     267     */
     268    public function data_converts_non_visible_characters_with_width_to_hyphen() {
     269        return array(
     270            // Only the non-visible characters.
     271            'only %e2%80%80'     => array( '%e2%80%80' ),
     272            'only %e2%80%81'     => array( '%e2%80%81' ),
     273            'only %e2%80%82'     => array( '%e2%80%82' ),
     274            'only %e2%80%83'     => array( '%e2%80%83' ),
     275            'only %e2%80%84'     => array( '%e2%80%84' ),
     276            'only %e2%80%85'     => array( '%e2%80%85' ),
     277            'only %e2%80%86'     => array( '%e2%80%86' ),
     278            'only %e2%80%87'     => array( '%e2%80%87' ),
     279            'only %e2%80%88'     => array( '%e2%80%88' ),
     280            'only %e2%80%89'     => array( '%e2%80%89' ),
     281            'only %e2%80%8a'     => array( '%e2%80%8a' ),
     282            'only %e2%80%a8'     => array( '%e2%80%a8' ),
     283            'only %e2%80%a9'     => array( '%e2%80%a9' ),
     284            'only %e2%80%af'     => array( '%e2%80%af' ),
     285
     286            // Non-visible characters within the title.
     287            'in middle of title' => array(
     288                'title'    => 'Nonvisible %e2%80%82 in middle of title',
     289                'expected' => 'nonvisible-in-middle-of-title',
     290            ),
     291            'at start of title'  => array(
     292                'title'    => '%e2%80%83Nonvisible at start of title',
     293                'expected' => 'nonvisible-at-start-of-title',
     294            ),
     295            'at end of title'    => array(
     296                'title'    => 'Nonvisible at end of title %e2%80%81',
     297                'expected' => 'nonvisible-at-end-of-title',
     298            ),
     299            'two end of title'   => array(
     300                'title'    => 'Nonvisible at end of title %e2%80%81 %e2%80%af',
     301                'expected' => 'nonvisible-at-end-of-title',
     302            ),
     303            'randomly in title'  => array(
     304                'title'    => 'Nonvisible%e2%80%80 %e2%80%a9randomly %e2%80%87in the %e2%80%a8 title%e2%80%af',
     305                'expected' => 'nonvisible-randomly-in-the-title',
     306            ),
     307        );
     308    }
     309
     310    /**
     311     * @ticket 47912
     312     * @dataProvider data_non_visible_characters_with_width_to_hyphen_when_not_save
     313     *
     314     * @param string $title     The title to be sanitized.
     315     * @param string $expected  Expected sanitized title.
     316     */
     317    public function test_non_visible_characters_with_width_to_hyphen_when_not_save( $title, $expected = '' ) {
     318        $this->assertSame( $expected, sanitize_title_with_dashes( $title ) );
     319    }
     320
     321    /**
     322     * Data provider.
     323     *
     324     * @return array
     325     */
     326    public function data_non_visible_characters_with_width_to_hyphen_when_not_save() {
     327        return array(
     328            // Just the non-visible characters.
     329            'only %e2%80%8b'     => array( '%e2%80%8b', '%e2%80%8b' ),
     330            'only %e2%80%8c'     => array( '%e2%80%8c', '%e2%80%8c' ),
     331            'only %e2%80%8d'     => array( '%e2%80%8d', '%e2%80%8d' ),
     332            'only %e2%80%8e'     => array( '%e2%80%8e', '%e2%80%8e' ),
     333            'only %e2%80%8f'     => array( '%e2%80%8f', '%e2%80%8f' ),
     334            'only %e2%80%aa'     => array( '%e2%80%aa', '%e2%80%aa' ),
     335            'only %e2%80%ab'     => array( '%e2%80%ab', '%e2%80%ab' ),
     336            'only %e2%80%ac'     => array( '%e2%80%ac', '%e2%80%ac' ),
     337            'only %e2%80%ad'     => array( '%e2%80%ad', '%e2%80%ad' ),
     338            'only %e2%80%ae'     => array( '%e2%80%ae', '%e2%80%ae' ),
     339            'only %ef%bb%bf'     => array( '%ef%bb%bf', '%ef%bb%bf' ),
     340
     341            // Non-visible characters within the title.
     342            'in middle of title' => array(
     343                'title'    => 'Nonvisible %e2%80%82 in middle of title',
     344                'expected' => 'nonvisible-%e2%80%82-in-middle-of-title',
     345            ),
     346            'at start of title'  => array(
     347                'title'    => '%e2%80%83Nonvisible at start of title',
     348                'expected' => '%e2%80%83nonvisible-at-start-of-title',
     349            ),
     350            'at end of title'    => array(
     351                'title'    => 'Nonvisible at end of title %e2%80%81',
     352                'expected' => 'nonvisible-at-end-of-title-%e2%80%81',
     353            ),
     354            'randomly in title'  => array(
     355                'title'    => 'Nonvisible%e2%80%80 %e2%80%aerandomly %e2%80%87in the %e2%80%a8 title%e2%80%af',
     356                'expected' => 'nonvisible%e2%80%80-%e2%80%aerandomly-%e2%80%87in-the-%e2%80%a8-title%e2%80%af',
     357            ),
     358        );
     359    }
    150360}
Note: See TracChangeset for help on using the changeset viewer.