Make WordPress Core

Changeset 56186


Ignore:
Timestamp:
07/10/2023 08:31:35 PM (3 years ago)
Author:
azaozz
Message:

Filesystem API: Ensure wp_tempnam() does not produce file names longer than 255 characters as this is the limit on most filesystems.

Props: costdev, doems, mikeschroder, oglekler, mrinal013.
Fixes: #35755.

Location:
trunk
Files:
2 edited

Legend:

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

    r56174 r56186  
    690690        $temp_filename .= '-' . wp_generate_password( 6, false );
    691691        $temp_filename .= '.tmp';
    692         $temp_filename  = $dir . wp_unique_filename( $dir, $temp_filename );
     692        $temp_filename  = wp_unique_filename( $dir, $temp_filename );
     693
     694        /*
     695         * Filesystems typically have a limit of 255 characters for a filename.
     696         *
     697         * If the generated unique filename exceeds this, truncate the initial
     698         * filename and try again.
     699         *
     700         * As it's possible that the truncated filename may exist, producing a
     701         * suffix of "-1" or "-10" which could exceed the limit again, truncate
     702         * it to 252 instead.
     703         */
     704        $characters_over_limit = strlen( $temp_filename ) - 252;
     705        if ( $characters_over_limit > 0 ) {
     706                $filename = substr( $filename, 0, -$characters_over_limit );
     707                return wp_tempnam( $filename, $dir );
     708        }
     709
     710        $temp_filename = $dir . $temp_filename;
    693711
    694712        $fp = @fopen( $temp_filename, 'x' );
  • trunk/tests/phpunit/tests/file.php

    r55060 r56186  
    207207
    208208        /**
     209         * Tests that `wp_tempnam()` limits the filename's length to 252 characters.
     210         *
     211         * @ticket 35755
     212         *
     213         * @covers ::wp_tempnam
     214         *
     215         * @dataProvider data_wp_tempnam_should_limit_filename_length_to_252_characters
     216         */
     217        public function test_wp_tempnam_should_limit_filename_length_to_252_characters( $filename ) {
     218                $file = wp_tempnam( $filename );
     219
     220                if ( file_exists( $file ) ) {
     221                        self::unlink( $file );
     222                }
     223
     224                $this->assertLessThanOrEqual( 252, strlen( basename( $file ) ) );
     225        }
     226
     227        /**
     228         * Data provider.
     229         *
     230         * @return array[]
     231         */
     232        public function data_wp_tempnam_should_limit_filename_length_to_252_characters() {
     233                return array(
     234                        'the limit before adding characters for uniqueness' => array( 'filename' => str_pad( '', 241, 'filename' ) ),
     235                        'one more than the limit before adding characters for uniqueness' => array( 'filename' => str_pad( '', 242, 'filename' ) ),
     236                        '251 characters' => array( 'filename' => str_pad( '', 251, 'filename' ) ),
     237                        '252 characters' => array( 'filename' => str_pad( '', 252, 'filename' ) ),
     238                        '253 characters' => array( 'filename' => str_pad( '', 253, 'filename' ) ),
     239                );
     240        }
     241
     242        /**
     243         * Tests that `wp_tempnam()` limits the filename's length to 252 characters
     244         * when there is a name conflict.
     245         *
     246         * @ticket 35755
     247         *
     248         * @covers ::wp_tempnam
     249         */
     250        public function test_wp_tempnam_should_limit_filename_length_to_252_characters_with_name_conflict() {
     251                // Create a conflict by removing the randomness of the generated password.
     252                add_filter(
     253                        'random_password',
     254                        static function() {
     255                                return '123456';
     256                        },
     257                        10,
     258                        0
     259                );
     260
     261                // A filename at the limit.
     262                $filename = str_pad( '', 252, 'filename' );
     263
     264                // Create the initial file.
     265                $existing_file = wp_tempnam( $filename );
     266
     267                // Try creating a file with the same name.
     268                $actual = wp_tempnam( basename( $existing_file ) );
     269
     270                self::unlink( $existing_file );
     271                self::unlink( $actual );
     272
     273                $this->assertLessThanOrEqual( 252, strlen( basename( $actual ) ) );
     274        }
     275
     276        /**
     277         * Tests that `wp_tempnam()` limits the filename's length to 252 characters
     278         * when a 'random_password' filter returns passwords longer than 6 characters.
     279         *
     280         * @ticket 35755
     281         *
     282         * @covers ::wp_tempnam
     283         */
     284        public function test_wp_tempnam_should_limit_filename_length_to_252_characters_when_random_password_is_filtered() {
     285                // Force random passwords to 12 characters.
     286                add_filter(
     287                        'random_password',
     288                        static function() {
     289                                return '1a2b3c4d5e6f';
     290                        },
     291                        10,
     292                        0
     293                );
     294
     295                // A filename at the limit.
     296                $filename = str_pad( '', 252, 'filename' );
     297                $actual   = wp_tempnam( $filename );
     298
     299                self::unlink( $actual );
     300
     301                $this->assertLessThanOrEqual( 252, strlen( basename( $actual ) ) );
     302        }
     303
     304        /**
     305         * Tests that `wp_tempnam()` limits the filename's length to 252 characters
     306         * when a 'wp_unique_filename' filter returns a filename longer than 252 characters.
     307         *
     308         * @ticket 35755
     309         *
     310         * @covers ::wp_tempnam
     311         */
     312        public function test_wp_tempnam_should_limit_filename_length_to_252_characters_when_wp_unique_filename_is_filtered() {
     313                // Determine the number of additional characters added by `wp_tempnam()`.
     314                $temp_dir                    = get_temp_dir();
     315                $additional_chars_filename   = wp_unique_filename( $temp_dir, 'filename' );
     316                $additional_chars_generated  = wp_tempnam( $additional_chars_filename, $temp_dir );
     317                $additional_chars_difference = strlen( basename( $additional_chars_generated ) ) - strlen( $additional_chars_filename );
     318
     319                $filenames_over_limit = 0;
     320
     321                // Make the filter send the filename over the limit.
     322                add_filter(
     323                        'wp_unique_filename',
     324                        static function( $filename ) use ( &$filenames_over_limit ) {
     325                                if ( strlen( $filename ) === 252 ) {
     326                                        $filename .= '1';
     327                                        ++$filenames_over_limit;
     328                                }
     329
     330                                return $filename;
     331                        },
     332                        10,
     333                        1
     334                );
     335
     336                // A filename that will hit the limit when `wp_tempnam()` adds characters.
     337                $filename = str_pad( '', 252 - $additional_chars_difference, 'filename' );
     338                $actual   = wp_tempnam( $filename );
     339
     340                self::unlink( $additional_chars_generated );
     341                self::unlink( $actual );
     342
     343                $this->assertLessThanOrEqual( 252, strlen( basename( $actual ) ), 'The final filename was over the limit.' );
     344                $this->assertSame( 1, $filenames_over_limit, 'One filename should have been over the limit.' );
     345        }
     346
     347        /**
     348         * Tests that `wp_tempnam()` limits the filename's length to 252 characters
     349         * when both a 'random_password' filter and a 'wp_unique_filename' filter
     350         * cause the filename to be greater than 252 characters.
     351         *
     352         * @ticket 35755
     353         *
     354         * @covers ::wp_tempnam
     355         */
     356        public function test_wp_tempnam_should_limit_filename_length_to_252_characters_when_random_password_and_wp_unique_filename_are_filtered() {
     357                // Force random passwords to 12 characters.
     358                add_filter(
     359                        'random_password',
     360                        static function() {
     361                                return '1a2b3c4d5e6f';
     362                        },
     363                        10,
     364                        0
     365                );
     366
     367                // Determine the number of additional characters added by `wp_tempnam()`.
     368                $temp_dir                    = get_temp_dir();
     369                $additional_chars_filename   = wp_unique_filename( $temp_dir, 'filename' );
     370                $additional_chars_generated  = wp_tempnam( $additional_chars_filename, $temp_dir );
     371                $additional_chars_difference = strlen( basename( $additional_chars_generated ) ) - strlen( $additional_chars_filename );
     372
     373                $filenames_over_limit = 0;
     374
     375                // Make the filter send the filename over the limit.
     376                add_filter(
     377                        'wp_unique_filename',
     378                        static function( $filename ) use ( &$filenames_over_limit ) {
     379                                if ( strlen( $filename ) === 252 ) {
     380                                        $filename .= '1';
     381                                        ++$filenames_over_limit;
     382                                }
     383
     384                                return $filename;
     385                        },
     386                        10,
     387                        1
     388                );
     389
     390                // A filename that will hit the limit when `wp_tempnam()` adds characters.
     391                $filename = str_pad( '', 252 - $additional_chars_difference, 'filename' );
     392                $actual   = wp_tempnam( $filename );
     393
     394                self::unlink( $additional_chars_generated );
     395                self::unlink( $actual );
     396
     397                $this->assertLessThanOrEqual( 252, strlen( basename( $actual ) ), 'The final filename was over the limit.' );
     398                $this->assertSame( 1, $filenames_over_limit, 'One filename should have been over the limit.' );
     399        }
     400
     401        /**
    209402         * @ticket 47186
    210403         */
Note: See TracChangeset for help on using the changeset viewer.