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.

git-svn-id: https://develop.svn.wordpress.org/trunk@56186 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Andrew Ozz 2023-07-10 20:31:35 +00:00
parent a92f7bf8cb
commit 8b8afa1299
2 changed files with 212 additions and 1 deletions

View File

@ -689,7 +689,25 @@ function wp_tempnam( $filename = '', $dir = '' ) {
// Suffix some random data to avoid filename conflicts.
$temp_filename .= '-' . wp_generate_password( 6, false );
$temp_filename .= '.tmp';
$temp_filename = $dir . wp_unique_filename( $dir, $temp_filename );
$temp_filename = wp_unique_filename( $dir, $temp_filename );
/*
* Filesystems typically have a limit of 255 characters for a filename.
*
* If the generated unique filename exceeds this, truncate the initial
* filename and try again.
*
* As it's possible that the truncated filename may exist, producing a
* suffix of "-1" or "-10" which could exceed the limit again, truncate
* it to 252 instead.
*/
$characters_over_limit = strlen( $temp_filename ) - 252;
if ( $characters_over_limit > 0 ) {
$filename = substr( $filename, 0, -$characters_over_limit );
return wp_tempnam( $filename, $dir );
}
$temp_filename = $dir . $temp_filename;
$fp = @fopen( $temp_filename, 'x' );

View File

@ -205,6 +205,199 @@ class Tests_File extends WP_UnitTestCase {
);
}
/**
* Tests that `wp_tempnam()` limits the filename's length to 252 characters.
*
* @ticket 35755
*
* @covers ::wp_tempnam
*
* @dataProvider data_wp_tempnam_should_limit_filename_length_to_252_characters
*/
public function test_wp_tempnam_should_limit_filename_length_to_252_characters( $filename ) {
$file = wp_tempnam( $filename );
if ( file_exists( $file ) ) {
self::unlink( $file );
}
$this->assertLessThanOrEqual( 252, strlen( basename( $file ) ) );
}
/**
* Data provider.
*
* @return array[]
*/
public function data_wp_tempnam_should_limit_filename_length_to_252_characters() {
return array(
'the limit before adding characters for uniqueness' => array( 'filename' => str_pad( '', 241, 'filename' ) ),
'one more than the limit before adding characters for uniqueness' => array( 'filename' => str_pad( '', 242, 'filename' ) ),
'251 characters' => array( 'filename' => str_pad( '', 251, 'filename' ) ),
'252 characters' => array( 'filename' => str_pad( '', 252, 'filename' ) ),
'253 characters' => array( 'filename' => str_pad( '', 253, 'filename' ) ),
);
}
/**
* Tests that `wp_tempnam()` limits the filename's length to 252 characters
* when there is a name conflict.
*
* @ticket 35755
*
* @covers ::wp_tempnam
*/
public function test_wp_tempnam_should_limit_filename_length_to_252_characters_with_name_conflict() {
// Create a conflict by removing the randomness of the generated password.
add_filter(
'random_password',
static function() {
return '123456';
},
10,
0
);
// A filename at the limit.
$filename = str_pad( '', 252, 'filename' );
// Create the initial file.
$existing_file = wp_tempnam( $filename );
// Try creating a file with the same name.
$actual = wp_tempnam( basename( $existing_file ) );
self::unlink( $existing_file );
self::unlink( $actual );
$this->assertLessThanOrEqual( 252, strlen( basename( $actual ) ) );
}
/**
* Tests that `wp_tempnam()` limits the filename's length to 252 characters
* when a 'random_password' filter returns passwords longer than 6 characters.
*
* @ticket 35755
*
* @covers ::wp_tempnam
*/
public function test_wp_tempnam_should_limit_filename_length_to_252_characters_when_random_password_is_filtered() {
// Force random passwords to 12 characters.
add_filter(
'random_password',
static function() {
return '1a2b3c4d5e6f';
},
10,
0
);
// A filename at the limit.
$filename = str_pad( '', 252, 'filename' );
$actual = wp_tempnam( $filename );
self::unlink( $actual );
$this->assertLessThanOrEqual( 252, strlen( basename( $actual ) ) );
}
/**
* Tests that `wp_tempnam()` limits the filename's length to 252 characters
* when a 'wp_unique_filename' filter returns a filename longer than 252 characters.
*
* @ticket 35755
*
* @covers ::wp_tempnam
*/
public function test_wp_tempnam_should_limit_filename_length_to_252_characters_when_wp_unique_filename_is_filtered() {
// Determine the number of additional characters added by `wp_tempnam()`.
$temp_dir = get_temp_dir();
$additional_chars_filename = wp_unique_filename( $temp_dir, 'filename' );
$additional_chars_generated = wp_tempnam( $additional_chars_filename, $temp_dir );
$additional_chars_difference = strlen( basename( $additional_chars_generated ) ) - strlen( $additional_chars_filename );
$filenames_over_limit = 0;
// Make the filter send the filename over the limit.
add_filter(
'wp_unique_filename',
static function( $filename ) use ( &$filenames_over_limit ) {
if ( strlen( $filename ) === 252 ) {
$filename .= '1';
++$filenames_over_limit;
}
return $filename;
},
10,
1
);
// A filename that will hit the limit when `wp_tempnam()` adds characters.
$filename = str_pad( '', 252 - $additional_chars_difference, 'filename' );
$actual = wp_tempnam( $filename );
self::unlink( $additional_chars_generated );
self::unlink( $actual );
$this->assertLessThanOrEqual( 252, strlen( basename( $actual ) ), 'The final filename was over the limit.' );
$this->assertSame( 1, $filenames_over_limit, 'One filename should have been over the limit.' );
}
/**
* Tests that `wp_tempnam()` limits the filename's length to 252 characters
* when both a 'random_password' filter and a 'wp_unique_filename' filter
* cause the filename to be greater than 252 characters.
*
* @ticket 35755
*
* @covers ::wp_tempnam
*/
public function test_wp_tempnam_should_limit_filename_length_to_252_characters_when_random_password_and_wp_unique_filename_are_filtered() {
// Force random passwords to 12 characters.
add_filter(
'random_password',
static function() {
return '1a2b3c4d5e6f';
},
10,
0
);
// Determine the number of additional characters added by `wp_tempnam()`.
$temp_dir = get_temp_dir();
$additional_chars_filename = wp_unique_filename( $temp_dir, 'filename' );
$additional_chars_generated = wp_tempnam( $additional_chars_filename, $temp_dir );
$additional_chars_difference = strlen( basename( $additional_chars_generated ) ) - strlen( $additional_chars_filename );
$filenames_over_limit = 0;
// Make the filter send the filename over the limit.
add_filter(
'wp_unique_filename',
static function( $filename ) use ( &$filenames_over_limit ) {
if ( strlen( $filename ) === 252 ) {
$filename .= '1';
++$filenames_over_limit;
}
return $filename;
},
10,
1
);
// A filename that will hit the limit when `wp_tempnam()` adds characters.
$filename = str_pad( '', 252 - $additional_chars_difference, 'filename' );
$actual = wp_tempnam( $filename );
self::unlink( $additional_chars_generated );
self::unlink( $actual );
$this->assertLessThanOrEqual( 252, strlen( basename( $actual ) ), 'The final filename was over the limit.' );
$this->assertSame( 1, $filenames_over_limit, 'One filename should have been over the limit.' );
}
/**
* @ticket 47186
*/