Make WordPress Core

Changeset 56186


Ignore:
Timestamp:
07/10/2023 08:31:35 PM (8 months 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.