Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1944ed1
fix(rest): cast string filesize meta to int
apermo Jul 20, 2026
ba9bf73
docs(rest): trim filesize guard comment per review
apermo Jul 20, 2026
dfb925e
Eliminate legacy args for create_object() and add assertions to fix P…
westonruter Jul 21, 2026
e694464
fix(rest): require positive integer filesize meta
apermo Jul 21, 2026
91c0463
fix(rest): guard ctype_digit against non-string meta
apermo Jul 21, 2026
12a4efa
Remove change to unrelated test
westonruter Jul 21, 2026
6d2882e
Add typing to data provider
westonruter Jul 21, 2026
be49854
Remove extra line break
westonruter Jul 21, 2026
d27aa4b
Ensure wp_filesize() never returns a negative number
westonruter Jul 21, 2026
a6810a9
Reformat multi-line conditional
westonruter Jul 21, 2026
e87ede8
Merge branch 'trunk' into trac-65670-attachment-filesize-typeerror
westonruter Jul 21, 2026
b6280e0
Make it clear a non-negative-int is required for the short-circuit to…
westonruter Jul 21, 2026
97bef96
Only cast numeric values to integer; treat others as zero
westonruter Jul 21, 2026
32585a8
Add tests
westonruter Jul 21, 2026
554a036
Improve formatting of arrow functions
westonruter Jul 21, 2026
62be93a
Update filesize key in metadata alone
westonruter Jul 21, 2026
001ee72
Use attachment's resolved path
westonruter Jul 21, 2026
c50756d
Relax attachment metadata filesize to include numeric values
westonruter Jul 21, 2026
7d71ec8
Improve wording of since tag
westonruter Jul 21, 2026
0808521
Improve name of test method
westonruter Jul 21, 2026
ec081cb
Indicate that filesize may be null in REST API response
westonruter Jul 21, 2026
bd24dfd
Improve data provider name
westonruter Jul 21, 2026
3d7ddf4
Add tests for numeric-string as wp_filesize and pre_wp_filesize filte…
westonruter Jul 21, 2026
f556a44
Clarify numeric value vs numeric string
westonruter Jul 21, 2026
026c8f0
Improve wording of wp_filesize() docblocks
westonruter Jul 21, 2026
6122b77
Add negative value test for pre_wp_filesize
westonruter Jul 21, 2026
866255d
Add data provider for negative pre_wp_filesize filter values
westonruter Jul 21, 2026
7d68239
Improve param name
westonruter Jul 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions src/wp-includes/functions.php

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed the typing of this function as otherwise get_attachment_filesize() would not be guaranteed to return a non-negative-int. This would warrant additional tests to ensure negative values get increased to zero.

Could there be any reason any plugin would filter pre_wp_filesize or wp_filesize to be negative?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides the obvious "Developer did a bad job, or had some quirky idea"?

I don't think so, but that alone is already bad enough.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No technically legitimate reason, but definitely a reason.

Original file line number Diff line number Diff line change
Expand Up @@ -3639,24 +3639,29 @@ function wp_get_ext_types() {
* Wrapper for PHP filesize with filters and casting the result as an integer.
*
* @since 6.0.0
* @since 7.1.0 The return value is now always zero or greater.
*
* @link https://www.php.net/manual/en/function.filesize.php
*
* @param string $path Path to the file.
* @return int The size of the file in bytes, or 0 in the event of an error.
* @phpstan-return non-negative-int
*/
function wp_filesize( $path ) {
function wp_filesize( $path ): int {
/**
* Filters the result of wp_filesize() before the file_exists() PHP function is run.
*
* @since 6.0.0
* @since 7.1.0 Negative values are now ignored, being treated the same as null. Numeric values are cast to integers.
*
* @param null|int $size The unfiltered value. Returning an int from the callback bypasses the filesize call.
* @param null|int $size The unfiltered value. Returning a non-negative number from the callback bypasses the filesize call.
* @param string $path Path to the file.
Comment thread
westonruter marked this conversation as resolved.
*/
$size = apply_filters( 'pre_wp_filesize', null, $path );

if ( is_int( $size ) ) {
if ( is_numeric( $size ) ) {
$size = (int) $size;
}
if ( is_int( $size ) && $size >= 0 ) {
Comment thread
westonruter marked this conversation as resolved.
return $size;
}

Expand All @@ -3666,11 +3671,18 @@ function wp_filesize( $path ) {
* Filters the size of the file.
*
* @since 6.0.0
* @since 7.1.0 The return value is now always zero or greater. Numeric values are cast to integers.
*
* @param int $size The result of PHP filesize on the file.
* @param string $path Path to the file.
*/
return (int) apply_filters( 'wp_filesize', $size, $path );
$size = apply_filters( 'wp_filesize', $size, $path );
if ( is_numeric( $size ) ) {
$size = (int) $size;
} else {
$size = 0;
}
return max( 0, $size );
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1707,7 +1707,7 @@ public function get_item_schema() {

$schema['properties']['filesize'] = array(
'description' => __( 'Attachment file size in bytes.' ),
'type' => 'integer',
'type' => array( 'integer', 'null' ),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude pointed out that null could be returned by the endpoint.

'context' => array( 'view', 'edit' ),
'readonly' => true,
);
Expand Down Expand Up @@ -2359,12 +2359,13 @@ protected function get_attachment_filename( int $attachment_id ): ?string {
*
* @param int $attachment_id Attachment ID.
* @return int|null Attachment file size in bytes, or null if not available.
* @phpstan-return non-negative-int|null
*/
protected function get_attachment_filesize( int $attachment_id ): ?int {
$meta = wp_get_attachment_metadata( $attachment_id );

if ( isset( $meta['filesize'] ) ) {
return $meta['filesize'];
if ( isset( $meta['filesize'] ) && is_numeric( $meta['filesize'] ) && $meta['filesize'] > 0 ) {
return (int) $meta['filesize'];
}

$original_path = wp_get_original_image_path( $attachment_id );
Expand Down
100 changes: 77 additions & 23 deletions tests/phpunit/tests/functions/wpFilesize.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,100 @@
*/
class Tests_Functions_wpFilesize extends WP_UnitTestCase {

const TEST_FILE = DIR_TESTDATA . '/images/test-image-upside-down.jpg';

/**
* @ticket 49412
*/
public function test_wp_filesize() {
$file = DIR_TESTDATA . '/images/test-image-upside-down.jpg';

$this->assertSame( filesize( $file ), wp_filesize( $file ) );
public function test_wp_filesize(): void {
$this->assertSame( filesize( self::TEST_FILE ), wp_filesize( self::TEST_FILE ) );
}

/**
* @ticket 49412
* @ticket 65670
*/
public function test_wp_filesize_filters() {
$file = DIR_TESTDATA . '/images/test-image-upside-down.jpg';
public function test_wp_filesize_filters(): void {
add_filter( 'wp_filesize', static fn () => 999 );
$this->assertSame( 999, wp_filesize( self::TEST_FILE ) );

add_filter(
'wp_filesize',
static function () {
return 999;
}
);
add_filter( 'wp_filesize', static fn () => '9991', 100 );
$this->assertSame( 9991, wp_filesize( self::TEST_FILE ) );

$this->assertSame( 999, wp_filesize( $file ) );
add_filter( 'pre_wp_filesize', static fn () => 111 );
$this->assertSame( 111, wp_filesize( self::TEST_FILE ) );

add_filter(
'pre_wp_filesize',
static function () {
return 111;
}
);
add_filter( 'pre_wp_filesize', static fn () => '2222', 100 );
$this->assertSame( 2222, wp_filesize( self::TEST_FILE ) );

$this->assertSame( 111, wp_filesize( $file ) );
add_filter( 'pre_wp_filesize', static fn () => -100, 200 );
$this->assertSame( 9991, wp_filesize( self::TEST_FILE ) );
}

/**
* @ticket 49412
*/
public function test_wp_filesize_with_nonexistent_file() {
$file = 'nonexistent/file.jpg';
public function test_wp_filesize_with_nonexistent_file(): void {
$this->assertSame( 0, wp_filesize( 'nonexistent/file.jpg' ) );
}

/**
* @ticket 65670
*/
public function test_wp_filesize_pre_wp_filesize_filter_null(): void {
add_filter( 'pre_wp_filesize', '__return_null' );

$this->assertSame( filesize( self::TEST_FILE ), wp_filesize( self::TEST_FILE ) );
}

$this->assertSame( 0, wp_filesize( $file ) );
/**
* @ticket 65670
*
* @dataProvider data_wp_filesize_pre_wp_filesize_filter_negative
*
* @param float|int|string $value Negative value returned by the filter.
*/
public function test_wp_filesize_pre_wp_filesize_filter_negative( $value ): void {
add_filter( 'pre_wp_filesize', static fn () => $value );

$this->assertSame( filesize( self::TEST_FILE ), wp_filesize( self::TEST_FILE ) );
}

/**
* Data provider.
*
* @return array<string, array{ 0: float|int|string }>
*/
public function data_wp_filesize_pre_wp_filesize_filter_negative(): array {
return array(
'negative int' => array( -1 ),
'negative numeric string' => array( '-1' ),
'negative float' => array( -1.5 ),
);
}

/**
* @ticket 65670
*
* @dataProvider data_wp_filesize_filter_invalid_value
*
* @param mixed $value
*/
public function test_wp_filesize_wp_filesize_filter_invalid_value( $value ): void {
add_filter( 'wp_filesize', static fn () => $value );
$this->assertSame( 0, wp_filesize( self::TEST_FILE ) );
}

/**
* Data provider.
*
* @return array<string, array{ 0: mixed }>
*/
public function data_wp_filesize_filter_invalid_value(): array {
return array(
'negative' => array( -1 ),
'null' => array( null ),
'array' => array( array( 'bad', 'array' ) ),
);
}
}
105 changes: 105 additions & 0 deletions tests/phpunit/tests/rest-api/rest-attachments-controller.php
Comment thread
apermo marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,111 @@ public function test_get_item() {
$this->assertSame( 'image/jpeg', $data['mime_type'] );
}

/**
* Ensures int-castable `filesize` values in attachment metadata are normalized
* to an integer in the response.
*
* Attachment metadata is untyped, so plugins that populate `filesize` from
* a remote storage API may store it as a string.
*
* @ticket 65670
*
* @dataProvider data_valid_filesize_meta
*
* @param mixed $stored_filesize Valid `filesize` metadata value.
* @param int $expected_filesize Expected `filesize` value in the REST response after normalization.
*/
public function test_get_item_normalizes_int_castable_filesize_meta( $stored_filesize, int $expected_filesize ) {
$attachment_id = self::factory()->attachment->create_object(
array(
'file' => self::$test_file,
'post_mime_type' => 'image/jpeg',
)
);
$this->assertIsInt( $attachment_id );

$meta = wp_get_attachment_metadata( $attachment_id );
$meta = is_array( $meta ) ? $meta : array();
$meta['filesize'] = $stored_filesize;
$this->assertNotFalse( wp_update_attachment_metadata( $attachment_id, $meta ) );

$request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $attachment_id );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();

$this->assertIsArray( $data );
$this->assertSame( 200, $response->get_status() );
$this->assertSame( $expected_filesize, $data['filesize'] );
}

/**
* Data provider.
*
* @return array<string, array{ 0: mixed, 1: int }>
*/
public function data_valid_filesize_meta(): array {
return array(
'integer string' => array( '123456', 123456 ),
'float string' => array( '123.4', 123 ),
'scientific notation' => array( '1e3', 1000 ),
'float' => array( 123.0, 123 ),
);
}

/**
* Ensures a `filesize` metadata value that is not a positive integer does
* not cause a fatal TypeError or a silently truncated size, falling back to
* the actual file size instead.
*
* @ticket 65670
*
* @dataProvider data_invalid_filesize_meta
*
* @param mixed $filesize Invalid `filesize` metadata value.
*/
public function test_get_item_recovers_from_invalid_filesize_meta( $filesize ) {
$attachment_id = self::factory()->attachment->create_object(
array(
'file' => self::$test_file,
'post_mime_type' => 'image/jpeg',
)
);
$this->assertIsInt( $attachment_id );

$meta = wp_get_attachment_metadata( $attachment_id );
$meta = is_array( $meta ) ? $meta : array();
$meta['filesize'] = $filesize;
$this->assertNotFalse( wp_update_attachment_metadata( $attachment_id, $meta ) );

$request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $attachment_id );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();

$this->assertIsArray( $data );
$this->assertSame( 200, $response->get_status() );
$this->assertIsInt( $data['filesize'] );
$attached_file = wp_get_original_image_path( $attachment_id );
$attached_file = $attached_file ? $attached_file : get_attached_file( $attachment_id );
$this->assertIsString( $attached_file );
$this->assertSame( filesize( $attached_file ), $data['filesize'] );
}

/**
* Data provider.
*
* @return array<string, array{ 0: mixed }>
*/
public function data_invalid_filesize_meta(): array {
return array(
'non-numeric string' => array( 'corrupt' ),
'boolean' => array( true ),
'zero' => array( 0 ),
'zero string' => array( '0' ),
'negative integer' => array( -5 ),
'negative integer string' => array( '-5' ),
);
Comment thread
westonruter marked this conversation as resolved.
}

/**
* @requires function imagejpeg
*/
Expand Down
Loading