Make WordPress Core

Changeset 51984


Ignore:
Timestamp:
11/02/2021 06:46:36 PM (5 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.