From 6025d75e29dc5a4ab7c30e9cc7faa75e80f8a282 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Mon, 2 May 2022 12:44:02 -0700 Subject: [PATCH 01/20] Add `wp_ini_bytes()` to report numeric php.ini directive values Partner to #55635 (https://core.trac.wordpress.org/ticket/55635) Resolves #17725 (https://core.trac.wordpress.org/ticket/17725) When wp_convert_hr_to_bytes() was introduced in [4388] it provided a simplified mechanism to parse the values returned by functions like ini_get() which represent byte sizes. The over-simplified approach has led to issues in that function reporting the wrong byte sizes for various php.ini directives, leading to confusing problems such as uploading files that are rejected improperly or accepted improperly. In this patch we're porting the parser from PHP's own source (which has remained stable for decades and probably can't change without major breakage) in order to more accurately reflect the values it uses when it reads those configurations. This is available in the new function `wp_ini_bytes()` found inside of `wp-includes/php-compat.php` and loaded automatically in `load.php`. Unfortunately PHP doesn't offer a mechanism to read its own internal value for these fields and a 100% port is extremely cumbersome (at best) due to the different ways that PHP and C handle signed integer overflow. These differences should only appear when supplying discouraged/invalid values to the system anyway, and PHP warns that in these situations things are likely to break anyway. --- src/wp-admin/includes/class-wp-debug-data.php | 2 +- .../includes/class-wp-site-health.php | 4 +- src/wp-includes/default-constants.php | 6 +- src/wp-includes/functions.php | 10 +- src/wp-includes/load.php | 15 ++ src/wp-includes/media.php | 4 +- src/wp-includes/php-compat.php | 250 ++++++++++++++++++ 7 files changed, 278 insertions(+), 13 deletions(-) create mode 100644 src/wp-includes/php-compat.php diff --git a/src/wp-admin/includes/class-wp-debug-data.php b/src/wp-admin/includes/class-wp-debug-data.php index ceae392fd2d3b..889f7223976c5 100644 --- a/src/wp-admin/includes/class-wp-debug-data.php +++ b/src/wp-admin/includes/class-wp-debug-data.php @@ -615,7 +615,7 @@ private static function get_wp_media(): array { $post_max_size = ini_get( 'post_max_size' ); $upload_max_filesize = ini_get( 'upload_max_filesize' ); $max_file_uploads = ini_get( 'max_file_uploads' ); - $effective = min( wp_convert_hr_to_bytes( $post_max_size ), wp_convert_hr_to_bytes( $upload_max_filesize ) ); + $effective = min( wp_ini_bytes( $post_max_size ), wp_ini_bytes( $upload_max_filesize ) ); // Add info in Media section. $fields['file_uploads'] = array( diff --git a/src/wp-admin/includes/class-wp-site-health.php b/src/wp-admin/includes/class-wp-site-health.php index dd537296a8655..dbcd157924cfa 100644 --- a/src/wp-admin/includes/class-wp-site-health.php +++ b/src/wp-admin/includes/class-wp-site-health.php @@ -2303,7 +2303,7 @@ public function get_test_file_uploads() { $post_max_size = ini_get( 'post_max_size' ); $upload_max_filesize = ini_get( 'upload_max_filesize' ); - if ( wp_convert_hr_to_bytes( $post_max_size ) < wp_convert_hr_to_bytes( $upload_max_filesize ) ) { + if ( wp_ini_bytes( $post_max_size ) < wp_ini_bytes( $upload_max_filesize ) ) { $result['label'] = sprintf( /* translators: 1: post_max_size, 2: upload_max_filesize */ __( 'The "%1$s" value is smaller than "%2$s"' ), @@ -2312,7 +2312,7 @@ public function get_test_file_uploads() { ); $result['status'] = 'recommended'; - if ( 0 === wp_convert_hr_to_bytes( $post_max_size ) ) { + if ( 0 === wp_ini_bytes( $post_max_size ) ) { $result['description'] = sprintf( '

%s

', sprintf( diff --git a/src/wp-includes/default-constants.php b/src/wp-includes/default-constants.php index acfc878fb7138..400a404ddf2bb 100644 --- a/src/wp-includes/default-constants.php +++ b/src/wp-includes/default-constants.php @@ -40,7 +40,7 @@ function wp_initial_constants() { } $current_limit = ini_get( 'memory_limit' ); - $current_limit_int = wp_convert_hr_to_bytes( $current_limit ); + $current_limit_int = wp_ini_bytes( $current_limit ); // Define memory limits. if ( ! defined( 'WP_MEMORY_LIMIT' ) ) { @@ -66,8 +66,8 @@ function wp_initial_constants() { } // Set memory limits. - $wp_limit_int = wp_convert_hr_to_bytes( WP_MEMORY_LIMIT ); - if ( -1 !== $current_limit_int && ( -1 === $wp_limit_int || $wp_limit_int > $current_limit_int ) ) { + $wp_limit_int = wp_ini_bytes( WP_MEMORY_LIMIT ); + if ( -1 !== $current_limit && ( -1 === $wp_limit_int || $wp_limit_int > $current_limit_int ) ) { ini_set( 'memory_limit', WP_MEMORY_LIMIT ); } diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 9cdeef75788f2..8d5afda4b76f2 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -7846,14 +7846,14 @@ function wp_raise_memory_limit( $context = 'admin' ) { } $current_limit = ini_get( 'memory_limit' ); - $current_limit_int = wp_convert_hr_to_bytes( $current_limit ); + $current_limit_int = wp_ini_bytes( $current_limit ); if ( -1 === $current_limit_int ) { return false; } $wp_max_limit = WP_MAX_MEMORY_LIMIT; - $wp_max_limit_int = wp_convert_hr_to_bytes( $wp_max_limit ); + $wp_max_limit_int = wp_ini_bytes( $wp_max_limit ); $filtered_limit = $wp_max_limit; switch ( $context ) { @@ -7929,15 +7929,15 @@ function wp_raise_memory_limit( $context = 'admin' ) { break; } - $filtered_limit_int = wp_convert_hr_to_bytes( $filtered_limit ); + $filtered_limit_int = wp_ini_bytes( $filtered_limit ); - if ( -1 === $filtered_limit_int || ( $filtered_limit_int > $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) { + if ( -1 === $filtered_limit || ( $filtered_limit_int > $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) { if ( false !== ini_set( 'memory_limit', $filtered_limit ) ) { return $filtered_limit; } else { return false; } - } elseif ( -1 === $wp_max_limit_int || $wp_max_limit_int > $current_limit_int ) { + } elseif ( -1 === $wp_max_limit || $wp_max_limit_int > $current_limit_int ) { if ( false !== ini_set( 'memory_limit', $wp_max_limit ) ) { return $wp_max_limit; } else { diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php index 90318acdddcb4..17e2438e0ea54 100644 --- a/src/wp-includes/load.php +++ b/src/wp-includes/load.php @@ -5,6 +5,8 @@ * @package WordPress */ +require_once __DIR__ . '/php-compat.php'; + /** * Returns the HTTP protocol sent by the server. * @@ -1677,6 +1679,7 @@ function is_ssl() { * * @since 2.3.0 * @since 4.6.0 Moved from media.php to load.php. + * @since 6.1.0 Deprecated: use wp_ini_bytes() or wp_hr_bytes() instead. * * @link https://www.php.net/manual/en/function.ini-get.php * @link https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes @@ -1685,6 +1688,18 @@ function is_ssl() { * @return int An integer byte value. */ function wp_convert_hr_to_bytes( $value ) { + return wp_hr_bytes( $value ); +} + +/** + * Converts a shorthand byte value to an integer byte value. + * + * @since 6.1.0 + * + * @param string $value Human-readable description of a byte size + * @return int An integer byte value. + */ +function wp_hr_bytes( $value ) { $value = strtolower( trim( $value ) ); $bytes = (int) $value; diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 6933ad69957e2..6ce6d2acae790 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -4185,8 +4185,8 @@ function wp_expand_dimensions( $example_width, $example_height, $max_width, $max * @return int Allowed upload size. */ function wp_max_upload_size() { - $u_bytes = wp_convert_hr_to_bytes( ini_get( 'upload_max_filesize' ) ); - $p_bytes = wp_convert_hr_to_bytes( ini_get( 'post_max_size' ) ); + $u_bytes = wp_ini_bytes( ini_get( 'upload_max_filesize' ) ); + $p_bytes = wp_ini_bytes( ini_get( 'post_max_size' ) ); /** * Filters the maximum upload size allowed in php.ini. diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php new file mode 100644 index 0000000000000..0cc432de23155 --- /dev/null +++ b/src/wp-includes/php-compat.php @@ -0,0 +1,250 @@ + 0, + '1' => 1, + '2' => 2, + '3' => 3, + '4' => 4, + '5' => 5, + '6' => 6, + '7' => 7, + '8' => 8, + '9' => 9, + 'A' => 10, + 'a' => 10, + 'B' => 11, + 'b' => 11, + 'C' => 12, + 'c' => 12, + 'D' => 13, + 'd' => 13, + 'E' => 14, + 'e' => 14, + 'F' => 15, + 'f' => 15, + ]; + + /* + * Build the scalar value by eating the next sequence of contiguous digits. + */ + for ( ; $i < $strlen; $i++) { + $c = $value[ $i ]; + + /* + * Only digits recognized in this base system can be used. + * Once we find an unrecognized digit we abort and move + * on to the next step in parsing the size suffix. + */ + if ( ! isset( $digits[ $c ] ) || $digits[ $c ] >= $base ) { + break; + } + + $scalar = $scalar * $base + $digits[ $c ]; + + // Stop processing if we're already at the max value. + if ( $scalar > PHP_INT_MAX ) { + break; + } + } + + // Clamp the parsed digits to an integer value as PHP does internally. + if ( $sign > 0 && $scalar >= PHP_INT_MAX ) { + $scalar = PHP_INT_MAX; + } else if ( $sign < 0 && $scalar >= -PHP_INT_MIN ) { + $scalar = PHP_INT_MIN; + } else if ( $sign < 0 ) { + $scalar = -$scalar; + } + + /* + * Do not use WP constants here (GB_IN_BYTES, MB_IN_BYTES, KB_IN_BYTES) + * since they are re-definable; PHP shorthand values are hard-coded + * in PHP itself and stay the same regardless of these constants. + * + * Note that we can overflow here, as happens in PHP itself. + * Overflow results will likely not match PHP's value, but + * will likely break in most cases anyway and so leaving + * this loose is the best we can do until and unless PHP + * makes a more concrete choice on how to handle overflow. + */ + switch ( $value[ $strlen - 1 ] ) { + case 'g': + case 'G': + $scalar *= 1073741824; // 1024^3 + break; + + case 'm': + case 'M': + $scalar *= 1048576; // 1024^2 + break; + + case 'k': + case 'K': + $scalar *= 1024; + break; + } + + return (int) $scalar; +} From 3e8e90f06ca05c93708c38960f61da666a85a58f Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Mon, 2 May 2022 12:53:06 -0700 Subject: [PATCH 02/20] Negative values can have a higher magnitude than positive values. --- src/wp-includes/php-compat.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index 0cc432de23155..5cec7c8b4ea44 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -204,7 +204,10 @@ function php_compat_ini_bytes( $value ) { $scalar = $scalar * $base + $digits[ $c ]; // Stop processing if we're already at the max value. - if ( $scalar > PHP_INT_MAX ) { + if ( + ( $sign > 0 && $scalar > PHP_INT_MAX ) || + ( $sign < 0 && $scalar > -PHP_INT_MIN ) + ) { break; } } From ca4ae65c1a4b08c6c42a92719c33f418219736f1 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Mon, 2 May 2022 16:18:46 -0700 Subject: [PATCH 03/20] Address style lint issues --- src/wp-includes/php-compat.php | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index 5cec7c8b4ea44..5653fabfc304e 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -1,5 +1,7 @@ 0, - '1' => 1, - '2' => 2, - '3' => 3, - '4' => 4, - '5' => 5, - '6' => 6, - '7' => 7, - '8' => 8, - '9' => 9, + $digits = array( + '0' => 0, + '1' => 1, + '2' => 2, + '3' => 3, + '4' => 4, + '5' => 5, + '6' => 6, + '7' => 7, + '8' => 8, + '9' => 9, 'A' => 10, 'a' => 10, 'B' => 11, @@ -184,12 +186,12 @@ function php_compat_ini_bytes( $value ) { 'e' => 14, 'F' => 15, 'f' => 15, - ]; + ); /* * Build the scalar value by eating the next sequence of contiguous digits. */ - for ( ; $i < $strlen; $i++) { + for ( ; $i < $strlen; $i++ ) { $c = $value[ $i ]; /* @@ -205,7 +207,7 @@ function php_compat_ini_bytes( $value ) { // Stop processing if we're already at the max value. if ( - ( $sign > 0 && $scalar > PHP_INT_MAX ) || + ( $sign > 0 && $scalar > PHP_INT_MAX ) || ( $sign < 0 && $scalar > -PHP_INT_MIN ) ) { break; From 00a74269c528e71e2810e99eb76d952a144a9ccc Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Mon, 2 May 2022 17:21:40 -0700 Subject: [PATCH 04/20] Stub out tests and check for PHP_INT_MAX --- src/wp-includes/php-compat.php | 4 +- .../phpunit/tests/load/wpConvertHrToBytes.php | 10 +- .../phpunit/tests/php-compat/wp_ini_size.php | 107 ++++++++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 tests/phpunit/tests/php-compat/wp_ini_size.php diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index 5653fabfc304e..a3c386c93de1b 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -1,6 +1,8 @@ assertEquals( 0, wp_ini_bytes( false ) ); + } + + public function test_absent_limit_is_no_limit() { + $this->assertEquals( 0, wp_ini_bytes( -1 ) ); + } + + public function test_invalid_data_is_no_limit() { + $this->assertEquals( 0, wp_ini_bytes( true ) ); + $this->assertEquals( 0, wp_ini_bytes( false ) ); + $this->assertEquals( 0, wp_ini_bytes( array( 1, 2, 3 ) ) ); + $this->assertEquals( 0, wp_ini_bytes( new stdClass ) ); + } + + public function test_returns_already_parsed_values() { + $this->assertEquals( 15, wp_ini_bytes( 15 ) ); + } + + public function test_clamped_to_max_int_before_suffix() { + if ( IS_32_BIT_SYSTEM ) { + $this->assertEquals( PHP_INT_MAX, wp_ini_bytes( '2147483648' ) ); + $this->assertEquals( PHP_INT_MIN, wp_ini_bytes( '-2147483649' ) ); + } else { + $this->assertEquals( PHP_INT_MAX, wp_ini_bytes( '9223372036854775808' ) ); + $this->assertEquals( PHP_INT_MIN, wp_ini_bytes( '-9223372036854775809' ) ); + } + } + + public function test_suffix_math_may_overflow() { + if ( IS_32_BIT_SYSTEM ) { + $this->assertNotEquals( PHP_INT_MAX, wp_ini_bytes( '2147483648g' ) ); + $this->assertNotEquals( PHP_INT_MIN, wp_ini_bytes( '-2147483648g' ) ); + } else { + $this->assertNotEquals( PHP_INT_MAX, wp_ini_bytes( '9223372036854775807g' ) ); + $this->assertNotEquals( PHP_INT_MIN, wp_ini_bytes( '-9223372036854775807g' ) ); + } + } + + /** + * Tests converting numeric php.ini directive strings into their scalar equivalents. + * + * @ticket 55635 + * + * @dataProvider data_php_numeric_strings + * + * @param $value + * @param $expected + */ + public function test_parse_matches_php_internal_value( $value, $expected ) { + $this->assertEquals( $expected, wp_ini_bytes( $value ) ); + } + + public function data_php_numeric_strings() { + return array( + // Decimal integer input. + array( '0', 0 ), + array( '100', 100 ), + array( '-14', -14 ), + + // Octal integer input. + array( '0100', 64 ), + array( '-0654', -428 ), + + // Hex input. + array( '0x14', 20 ), + array( '0X14', 20 ), + array( '-0xAA', -170 ), + + // Size suffixes. + array( '1g', 1073741824 ), + array( '32k', 32768 ), + array( '64K', 65536 ), + array( '07k', 7168 ), + array( '-0xF3d7m', -65455259648 ), + array( '128m', 134217728 ), + array( '256M', 268435456 ), + + // Leading characters. + array( ' 68', 68 ), + array( '+1', 1 ), + array( ' -0xdeadbeef', -3735928559 ), + array( ' 00000077', 63 ), + + // Things that don't look valid but are still possible. + array( '', 0 ), + array( '3km', 3145728 ), + array( '1mg', 1073741824 ), + array( 'boat', 0 ), + array( '-14chairsk', -14336 ), + array( '0xt', 0 ), + array( '++3', 0 ), + array( '0x5ome 🅰🅱🅲 attack', 5120 ), + ); + } +} From 2009da768a6674f2a0823f76c24e1f7ce1cdd144 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Mon, 2 May 2022 17:22:43 -0700 Subject: [PATCH 05/20] fixup! Stub out tests and check for PHP_INT_MAX --- src/wp-includes/php-compat.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index a3c386c93de1b..06d582298ea44 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -1,8 +1,8 @@ Date: Mon, 2 May 2022 17:24:15 -0700 Subject: [PATCH 06/20] fixup! Stub out tests and check for PHP_INT_MAX --- src/wp-includes/php-compat.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index 06d582298ea44..2ec8baff5f71a 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -1,8 +1,8 @@ Date: Mon, 2 May 2022 17:30:25 -0700 Subject: [PATCH 07/20] fixup! Stub out tests and check for PHP_INT_MAX --- src/wp-includes/php-compat.php | 6 +++--- tests/phpunit/tests/php-compat/wp_ini_size.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index 2ec8baff5f71a..0c334ef311263 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -1,8 +1,8 @@ Date: Mon, 2 May 2022 17:39:04 -0700 Subject: [PATCH 08/20] fixup! Stub out tests and check for PHP_INT_MAX --- tests/phpunit/tests/php-compat/wp_ini_size.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/phpunit/tests/php-compat/wp_ini_size.php b/tests/phpunit/tests/php-compat/wp_ini_size.php index 587a289f3c33f..2f7cfb394cf7b 100644 --- a/tests/phpunit/tests/php-compat/wp_ini_size.php +++ b/tests/phpunit/tests/php-compat/wp_ini_size.php @@ -85,6 +85,10 @@ public function data_php_numeric_strings() { array( '07k', 7168 ), array( '-0xF3d7m', -65455259648 ), array( '128m', 134217728 ), + array( '128m ', 128 ), + array( '128mk', 131072 ), + array( '128km', 134217728 ), + array( '1.28 kmg', 1073741824 ), array( '256M', 268435456 ), // Leading characters. From 218fe037a3b6459fe3058aa2b04fecc3115238d9 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Mon, 2 May 2022 17:39:55 -0700 Subject: [PATCH 09/20] fixup! Stub out tests and check for PHP_INT_MAX --- tests/phpunit/tests/php-compat/wp_ini_size.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/phpunit/tests/php-compat/wp_ini_size.php b/tests/phpunit/tests/php-compat/wp_ini_size.php index 2f7cfb394cf7b..4948e34f2e009 100644 --- a/tests/phpunit/tests/php-compat/wp_ini_size.php +++ b/tests/phpunit/tests/php-compat/wp_ini_size.php @@ -80,6 +80,7 @@ public function data_php_numeric_strings() { // Size suffixes. array( '1g', 1073741824 ), + array( '1gb', 0 ), array( '32k', 32768 ), array( '64K', 65536 ), array( '07k', 7168 ), From ac81d15a4d21d96d57d4ed3384fe66a43e6baf88 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Wed, 4 May 2022 13:30:10 -0700 Subject: [PATCH 10/20] Update src/wp-includes/load.php Co-authored-by: Ayesh Karunaratne --- src/wp-includes/load.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php index 17e2438e0ea54..978f49bab71da 100644 --- a/src/wp-includes/load.php +++ b/src/wp-includes/load.php @@ -1679,7 +1679,7 @@ function is_ssl() { * * @since 2.3.0 * @since 4.6.0 Moved from media.php to load.php. - * @since 6.1.0 Deprecated: use wp_ini_bytes() or wp_hr_bytes() instead. + * @deprecated 6.1.0 Use wp_ini_bytes() or wp_hr_bytes() instead. * * @link https://www.php.net/manual/en/function.ini-get.php * @link https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes From a8b1189fb0301d75089ce0f7e568c4c2b26cb61c Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Fri, 13 May 2022 12:28:05 -0700 Subject: [PATCH 11/20] Introduce comparison functions to avoid performing math on php.ini quantity values --- src/wp-admin/includes/class-wp-debug-data.php | 2 +- .../includes/class-wp-site-health.php | 4 +- src/wp-includes/default-constants.php | 10 +- src/wp-includes/functions.php | 37 ++-- src/wp-includes/load.php | 2 +- src/wp-includes/media.php | 6 +- src/wp-includes/php-compat.php | 177 ++++++++++++------ .../phpunit/tests/load/wpConvertHrToBytes.php | 2 +- ...wp_ini_size.php => ini_parse_quantity.php} | 38 ++-- 9 files changed, 162 insertions(+), 116 deletions(-) rename tests/phpunit/tests/php-compat/{wp_ini_size.php => ini_parse_quantity.php} (60%) diff --git a/src/wp-admin/includes/class-wp-debug-data.php b/src/wp-admin/includes/class-wp-debug-data.php index 889f7223976c5..4d754a26457fb 100644 --- a/src/wp-admin/includes/class-wp-debug-data.php +++ b/src/wp-admin/includes/class-wp-debug-data.php @@ -615,7 +615,7 @@ private static function get_wp_media(): array { $post_max_size = ini_get( 'post_max_size' ); $upload_max_filesize = ini_get( 'upload_max_filesize' ); $max_file_uploads = ini_get( 'max_file_uploads' ); - $effective = min( wp_ini_bytes( $post_max_size ), wp_ini_bytes( $upload_max_filesize ) ); + $effective = wp_ini_quantity_min( $post_max_size, $upload_max_filesize ); // Add info in Media section. $fields['file_uploads'] = array( diff --git a/src/wp-admin/includes/class-wp-site-health.php b/src/wp-admin/includes/class-wp-site-health.php index dbcd157924cfa..a1d10555efe4a 100644 --- a/src/wp-admin/includes/class-wp-site-health.php +++ b/src/wp-admin/includes/class-wp-site-health.php @@ -2303,7 +2303,7 @@ public function get_test_file_uploads() { $post_max_size = ini_get( 'post_max_size' ); $upload_max_filesize = ini_get( 'upload_max_filesize' ); - if ( wp_ini_bytes( $post_max_size ) < wp_ini_bytes( $upload_max_filesize ) ) { + if ( wp_ini_quantity_cmp( $post_max_size, $upload_max_filesize ) < 0 ) { $result['label'] = sprintf( /* translators: 1: post_max_size, 2: upload_max_filesize */ __( 'The "%1$s" value is smaller than "%2$s"' ), @@ -2312,7 +2312,7 @@ public function get_test_file_uploads() { ); $result['status'] = 'recommended'; - if ( 0 === wp_ini_bytes( $post_max_size ) ) { + if ( 0 === wp_ini_parse_quantity( $post_max_size ) ) { $result['description'] = sprintf( '

%s

', sprintf( diff --git a/src/wp-includes/default-constants.php b/src/wp-includes/default-constants.php index 400a404ddf2bb..9918012a184f7 100644 --- a/src/wp-includes/default-constants.php +++ b/src/wp-includes/default-constants.php @@ -39,8 +39,7 @@ function wp_initial_constants() { define( 'WP_START_TIMESTAMP', microtime( true ) ); } - $current_limit = ini_get( 'memory_limit' ); - $current_limit_int = wp_ini_bytes( $current_limit ); + $current_limit = ini_get( 'memory_limit' ); // Define memory limits. if ( ! defined( 'WP_MEMORY_LIMIT' ) ) { @@ -56,18 +55,15 @@ function wp_initial_constants() { if ( ! defined( 'WP_MAX_MEMORY_LIMIT' ) ) { if ( false === wp_is_ini_value_changeable( 'memory_limit' ) ) { define( 'WP_MAX_MEMORY_LIMIT', $current_limit ); - } elseif ( -1 === $current_limit_int || $current_limit_int > 256 * MB_IN_BYTES ) { + } elseif ( wp_ini_quantity_cmp( $current_limit, '256M' ) > 0 ) { define( 'WP_MAX_MEMORY_LIMIT', $current_limit ); - } elseif ( wp_convert_hr_to_bytes( WP_MEMORY_LIMIT ) > 256 * MB_IN_BYTES ) { - define( 'WP_MAX_MEMORY_LIMIT', WP_MEMORY_LIMIT ); } else { define( 'WP_MAX_MEMORY_LIMIT', '256M' ); } } // Set memory limits. - $wp_limit_int = wp_ini_bytes( WP_MEMORY_LIMIT ); - if ( -1 !== $current_limit && ( -1 === $wp_limit_int || $wp_limit_int > $current_limit_int ) ) { + if ( wp_ini_quantity_cmp( WP_MEMORY_LIMIT, $current_limit ) > 0 ) { ini_set( 'memory_limit', WP_MEMORY_LIMIT ); } diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 8d5afda4b76f2..362c53a26c784 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -7845,16 +7845,14 @@ function wp_raise_memory_limit( $context = 'admin' ) { return false; } - $current_limit = ini_get( 'memory_limit' ); - $current_limit_int = wp_ini_bytes( $current_limit ); + $current_limit = ini_get( 'memory_limit' ); - if ( -1 === $current_limit_int ) { + if ( 0 === wp_ini_parse_quantity( $current_limit ) ) { return false; } - $wp_max_limit = WP_MAX_MEMORY_LIMIT; - $wp_max_limit_int = wp_ini_bytes( $wp_max_limit ); - $filtered_limit = $wp_max_limit; + $wp_max_limit = WP_MAX_MEMORY_LIMIT; + $filtered_limit = $wp_max_limit; switch ( $context ) { case 'admin': @@ -7929,20 +7927,19 @@ function wp_raise_memory_limit( $context = 'admin' ) { break; } - $filtered_limit_int = wp_ini_bytes( $filtered_limit ); - - if ( -1 === $filtered_limit || ( $filtered_limit_int > $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) { - if ( false !== ini_set( 'memory_limit', $filtered_limit ) ) { - return $filtered_limit; - } else { - return false; - } - } elseif ( -1 === $wp_max_limit || $wp_max_limit_int > $current_limit_int ) { - if ( false !== ini_set( 'memory_limit', $wp_max_limit ) ) { - return $wp_max_limit; - } else { - return false; - } + if ( + wp_ini_quantity_cmp( $filtered_limit, WP_MAX_MEMORY_LIMIT ) > 0 && + wp_ini_quantity_cmp( $filtered_limit, $current_limit ) > 0 + ) { + return false !== ini_set( 'memory_limit', $filtered_limit ) + ? $filtered_limit + : false; + } elseif ( + wp_ini_quantity_cmp( WP_MAX_MEMORY_LIMIT, $current_limit ) > 0 + ) { + return false !== ini_set( 'memory_limit', WP_MAX_MEMORY_LIMIT ) + ? WP_MAX_MEMORY_LIMIT + : false; } return false; diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php index 978f49bab71da..8b132b263a85e 100644 --- a/src/wp-includes/load.php +++ b/src/wp-includes/load.php @@ -1679,7 +1679,7 @@ function is_ssl() { * * @since 2.3.0 * @since 4.6.0 Moved from media.php to load.php. - * @deprecated 6.1.0 Use wp_ini_bytes() or wp_hr_bytes() instead. + * @deprecated 6.1.0 Use wp_ini_parse_quantity() or wp_hr_bytes() instead. * * @link https://www.php.net/manual/en/function.ini-get.php * @link https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 6ce6d2acae790..2507bc6e8753d 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -4185,8 +4185,8 @@ function wp_expand_dimensions( $example_width, $example_height, $max_width, $max * @return int Allowed upload size. */ function wp_max_upload_size() { - $u_bytes = wp_ini_bytes( ini_get( 'upload_max_filesize' ) ); - $p_bytes = wp_ini_bytes( ini_get( 'post_max_size' ) ); + $u_bytes = wp_ini_parse_quantity( ini_get( 'upload_max_filesize' ) ); + $p_bytes = wp_ini_parse_quantity( ini_get( 'post_max_size' ) ); /** * Filters the maximum upload size allowed in php.ini. @@ -4197,7 +4197,7 @@ function wp_max_upload_size() { * @param int $u_bytes Maximum upload filesize in bytes. * @param int $p_bytes Maximum size of POST data in bytes. */ - return apply_filters( 'upload_size_limit', min( $u_bytes, $p_bytes ), $u_bytes, $p_bytes ); + return apply_filters( 'upload_size_limit', wp_ini_quantity_min( $u_bytes, $p_bytes ), $u_bytes, $p_bytes ); } /** diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index 0c334ef311263..020f284863074 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -1,28 +1,37 @@ = 0 ? $a : $b; } -function wp_ini_bytes_is_present( $value ) { - return -1 !== $value && false !== $value; +function wp_ini_quantity_min( $a, $b ) { + return wp_ini_quantity_cmp( $a, $b ) <= 0 ? $a : $b; } /** - * Returns byte size represented in a numeric php.ini directive. + * Comparator for php.ini quantity values, can be used + * as the callback for functions such as `usort()`. + * + * Example: + * $a < $b => -1 + * $a === $b => 0 + * $a > $b => 1 + * + * @param int|string|false $a Quantity being compared. + * @param int|string|false $b Quantity against which $a is compared. + * @return int + */ +function wp_ini_quantity_cmp( $a, $b ) { + $a_scalar = wp_ini_parse_quantity( $a ); + $b_scalar = wp_ini_parse_quantity( $b ); + + if ( $a_scalar === $b_scalar ) { + return 0; + } + + // No limit on $a means it's at least as big as $b. + if ( 0 === $a_scalar ) { + return 1; + } + + // Any limit on $a means it's smaller than a no-limit $b. + if ( 0 === $b_scalar ) { + return -1; + } + + return $a_scalar > $b_scalar ? 1 : -1; +} + +if ( ! function_exists( 'ini_parse_quantity' ) ): +/** + * Returns quantity represented by a php.ini directive's "byte size shorthand." * * php.ini directives may use a string representation of a number of bytes * or a "shorthand" byte size to reference larger values. Multiple numeric @@ -49,52 +97,56 @@ function wp_ini_bytes_is_present( $value ) { * * Example: * - * php_compat_ini_bytes( "1m" ) == 1048576 - * php_compat_ini_bytes( "2K" ) == 2048 // 2 * 1024 - * php_compat_ini_bytes( "0.5g" ) == 0 - * php_compat_ini_bytes( "14.6e-13g" ) == 15032385536 // 14 * 1024^3 - * php_compat_ini_bytes( "-813k" ) == 0; - * php_compat_ini_bytes( "boat" ) == 0; + * ini_parse_quantity( "1m" ) == 1048576 + * ini_parse_quantity( "2K" ) == 2048 // 2 * 1024 + * ini_parse_quantity( "0.5g" ) == 0 + * ini_parse_quantity( "14.6e-13g" ) == 15032385536 // 14 * 1024^3 + * ini_parse_quantity( "-813k" ) == 0; + * ini_parse_quantity( "boat" ) == 0; * * // This gives an answer, but it's _wrong_ because * // the underlying mechanism in PHP overflowed and * // the real return value depends on whether PHP * // was built with 64-bit support. - * php_compat_ini_bytes( "9223372036854775807g" ) == ?? + * ini_parse_quantity( "9223372036854775807g" ) == ?? * * Notes: - * - Suffix units are case-insensitive and are always determined - * by looking at the last character in the input string. - * - Suffix units k/m/g report powers of 1024. PHP and the IEC disagree - * on the meaning of "kilobyte," "megabyte," and "gigabyte." - * - This function will not fail; it stops parsing after finding - * the last consecutive digit at the front of the trimmed string. - * - Invalid string representations return a value of 0. - * - As noted in the PHP documentation, any numeric value that overflows - * an integer for the platform on which PHP is built will break. + * - Suffixes are specifically _the last character_ and case-insensitive. + * - Suffixes k/m/g intentionally report powers of 1024 to agree with PHP. + * - This function does not fail on invalid input; it returns `0` in such cses. + * - As noted in the PHP documentation, overflow behavior is unspecified and + * platform-dependant. Values that trigger overflow are likely wrong * - * @since 6.1.0 + * @since 6.1. * * @link https://www.php.net/manual/en/function.ini-get.php * @link https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes - * @link https://en.wikipedia.org/wiki/Byte#Multiple-byte_units - * - * @param string $value A numeric php.ini directive's byte value, - * either shorthand or ordinary, as returned - * by a call to `ini_get()`. + + * @param string $value Numeric string value possibly in "shorthand notation." * @return int Parsed numeric value represented by given string. */ -function php_compat_ini_bytes( $value ) { - /** @var int Number of bytes in input string; we're only assessing 7-bit ASCII/Unicode characters. */ +function ini_parse_quantity( $value ) { + /** + * Number of bytes in input string; because we're only assessing 7-bit + * ASCII/Unicode characters we can safely count bytes vs. needing to + * worry about code units, code points, or grapheme clusters. + * + * @var int + */ $strlen = strlen( $value ); - /** @var int|float Count (of bytes) represented by value string. */ + /** @var int|float Numeric quantity represented by value string. */ $scalar = 0; - /** @var int Sign of number represented by input, either positive (1) or negative (-1). */ + /** @var int Sign of numeric quantity, either positive (1) or negative (-1). */ $sign = 1; - /** @var int Numeric base of digits; determined by string prefix (e.g. "0x" or "0"). */ + /** + * Numeric base of digits determined by string prefix (e.g. "0x" or "0"). + * Must be 8 for octal, 10 for decimal, or 16 for hexadecimal. + * + * @var int + */ $base = 10; /** @var int Index into input string as we walk through it and analyze each character. */ @@ -110,17 +162,13 @@ function php_compat_ini_bytes( $value ) { * `+` or `-` characters in a row. */ for ( ; $i < $strlen; $i++ ) { - switch ( $value[ $i ] ) { - case ' ': - case "\t": - case "\r": - case "\v": - case "\f": - break; - - default: - break 2; + $c = $value[ $i ]; + + if ( ' ' === $c || "\t" === $c || "\r" === $c || "\v" === $c || "\f" === $c ) { + continue; } + + break; } // Handle optional sign indicator. @@ -157,11 +205,11 @@ function php_compat_ini_bytes( $value ) { /** * Numeric values for scanned digits. * - * These are used both to determine the decimal value the digit - * represents as well as whether it's an allowed character in - * the given base system. It's allowed if its value is less - * than the base: e.g. '7' is allowed in octal, "base 8" - * but '8' and '9' aren't because they are above it. + * These are used to determine the decimal value the digit + * represents and whether it's an allowed character in + * the given base. It's allowed if its value is less + * than the base: e.g. '7' is allowed in octal (base 8) + * but '8' and '9' aren't because they are greater than 8. * * @var array */ @@ -190,9 +238,7 @@ function php_compat_ini_bytes( $value ) { 'f' => 15, ); - /* - * Build the scalar value by eating the next sequence of contiguous digits. - */ + // Build the scalar value by consuming the next sequence of contiguous digits. for ( ; $i < $strlen; $i++ ) { $c = $value[ $i ]; @@ -205,9 +251,17 @@ function php_compat_ini_bytes( $value ) { break; } + /* + * This is the step that computes our integer as we see new digits. + * + * Example: + * 4 = (0 * 10) + 4 + * 45 = ((0 * 10 + 4) * 10) + 5 + * 458 = ((0 * 10 + 4) * 10 + 5) * 10 + 8 + */ $scalar = $scalar * $base + $digits[ $c ]; - // Stop processing if we're already at the max value. + // Stop processing if we're already at the maximum magnitude for the sign. if ( ( $sign > 0 && $scalar > PHP_INT_MAX ) || ( $sign < 0 && $scalar > -PHP_INT_MIN ) @@ -233,8 +287,8 @@ function php_compat_ini_bytes( $value ) { * Note that we can overflow here, as happens in PHP itself. * Overflow results will likely not match PHP's value, but * will likely break in most cases anyway and so leaving - * this loose is the best we can do until and unless PHP - * makes a more concrete choice on how to handle overflow. + * this loose is the best we can do until we can read these + * values directly from PHP. */ switch ( $value[ $strlen - 1 ] ) { case 'g': @@ -255,3 +309,4 @@ function php_compat_ini_bytes( $value ) { return (int) $scalar; } +endif; diff --git a/tests/phpunit/tests/load/wpConvertHrToBytes.php b/tests/phpunit/tests/load/wpConvertHrToBytes.php index b1603372be4d7..6ff51799d0b7e 100644 --- a/tests/phpunit/tests/load/wpConvertHrToBytes.php +++ b/tests/phpunit/tests/load/wpConvertHrToBytes.php @@ -50,7 +50,7 @@ public function data_wp_convert_hr_to_bytes() { * Note that this is not the value that PHP uses internally. * PHP interprets the value as 128, not 128 MiB. * - * @see wp_ini_bytes() + * @see wp_ini_parse_quantity() */ array( '128m ', 134217728 ), array( '1024', 1024 ), // No letter will be interpreted as integer value. diff --git a/tests/phpunit/tests/php-compat/wp_ini_size.php b/tests/phpunit/tests/php-compat/ini_parse_quantity.php similarity index 60% rename from tests/phpunit/tests/php-compat/wp_ini_size.php rename to tests/phpunit/tests/php-compat/ini_parse_quantity.php index 4948e34f2e009..02bae79393ff8 100644 --- a/tests/phpunit/tests/php-compat/wp_ini_size.php +++ b/tests/phpunit/tests/php-compat/ini_parse_quantity.php @@ -1,50 +1,48 @@ assertEquals( 0, wp_ini_bytes( false ) ); + $this->assertEquals( 0, wp_ini_parse_quantity( false ) ); } public function test_absent_limit_is_no_limit() { - $this->assertEquals( 0, wp_ini_bytes( -1 ) ); + $this->assertEquals( 0, wp_ini_parse_quantity( -1 ) ); } public function test_invalid_data_is_no_limit() { - $this->assertEquals( 0, wp_ini_bytes( true ) ); - $this->assertEquals( 0, wp_ini_bytes( false ) ); - $this->assertEquals( 0, wp_ini_bytes( array( 1, 2, 3 ) ) ); - $this->assertEquals( 0, wp_ini_bytes( new stdClass ) ); + $this->assertEquals( 0, wp_ini_parse_quantity( true ) ); + $this->assertEquals( 0, wp_ini_parse_quantity( false ) ); + $this->assertEquals( 0, wp_ini_parse_quantity( array( 1, 2, 3 ) ) ); + $this->assertEquals( 0, wp_ini_parse_quantity( new stdClass ) ); } public function test_returns_already_parsed_values() { - $this->assertEquals( 15, wp_ini_bytes( 15 ) ); + $this->assertEquals( 15, wp_ini_parse_quantity( 15 ) ); } public function test_clamped_to_max_int_before_suffix() { if ( IS_32_BIT_SYSTEM ) { - $this->assertEquals( PHP_INT_MAX, wp_ini_bytes( '2147483648' ) ); - $this->assertEquals( PHP_INT_MIN, wp_ini_bytes( '-2147483649' ) ); + $this->assertEquals( PHP_INT_MAX, wp_ini_parse_quantity( '2147483648' ) ); + $this->assertEquals( PHP_INT_MIN, wp_ini_parse_quantity( '-2147483649' ) ); } else { - $this->assertEquals( PHP_INT_MAX, wp_ini_bytes( '9223372036854775808' ) ); - $this->assertEquals( PHP_INT_MIN, wp_ini_bytes( '-9223372036854775809' ) ); + $this->assertEquals( PHP_INT_MAX, wp_ini_parse_quantity( '9223372036854775808' ) ); + $this->assertEquals( PHP_INT_MIN, wp_ini_parse_quantity( '-9223372036854775809' ) ); } } public function test_suffix_math_may_overflow() { if ( IS_32_BIT_SYSTEM ) { - $this->assertNotEquals( PHP_INT_MAX, wp_ini_bytes( '2147483648g' ) ); - $this->assertNotEquals( PHP_INT_MIN, wp_ini_bytes( '-2147483648g' ) ); + $this->assertNotEquals( PHP_INT_MAX, wp_ini_parse_quantity( '2147483648g' ) ); + $this->assertNotEquals( PHP_INT_MIN, wp_ini_parse_quantity( '-2147483648g' ) ); } else { - $this->assertNotEquals( PHP_INT_MAX, wp_ini_bytes( '9223372036854775807g' ) ); - $this->assertNotEquals( PHP_INT_MIN, wp_ini_bytes( '-9223372036854775807g' ) ); + $this->assertNotEquals( PHP_INT_MAX, wp_ini_parse_quantity( '9223372036854775807g' ) ); + $this->assertNotEquals( PHP_INT_MIN, wp_ini_parse_quantity( '-9223372036854775807g' ) ); } } @@ -59,7 +57,7 @@ public function test_suffix_math_may_overflow() { * @param $expected */ public function test_parse_matches_php_internal_value( $value, $expected ) { - $this->assertEquals( $expected, wp_ini_bytes( $value ) ); + $this->assertEquals( $expected, wp_ini_parse_quantity( $value ) ); } public function data_php_numeric_strings() { From d52fd4a173fc306d1434c75c2af513b4d37bc028 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Fri, 13 May 2022 12:31:34 -0700 Subject: [PATCH 12/20] Add call to _deprecated_function --- src/wp-includes/load.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php index 8b132b263a85e..ab6c301bf1936 100644 --- a/src/wp-includes/load.php +++ b/src/wp-includes/load.php @@ -1688,11 +1688,12 @@ function is_ssl() { * @return int An integer byte value. */ function wp_convert_hr_to_bytes( $value ) { + _deprecated_function( __FUNCTION__, '6.1.0', 'wp_ini_parse_quantity' ); return wp_hr_bytes( $value ); } /** - * Converts a shorthand byte value to an integer byte value. + * Parses a "human-readable" byte value into an integer. * * @since 6.1.0 * From a6914304d9d485df38315626f4d18d21bfe7b962 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Fri, 13 May 2022 12:42:28 -0700 Subject: [PATCH 13/20] style issues --- src/wp-includes/php-compat.php | 413 +++++++++++++++++---------------- 1 file changed, 219 insertions(+), 194 deletions(-) diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index 020f284863074..15449d72e9dac 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -45,10 +45,34 @@ function wp_ini_parse_quantity( $value ) { return ini_parse_quantity( $value ); } +/** + * Returns larger of two php.ini directive quantity values. + * + * Example: + * wp_ini_quantity_max( '256m', -1 ) === -1 + * wp_ini_quantity_max( '64K', '64') === '64K' + * wp_ini_qantity_max( 1000, 2000 ) === 2000 + * + * @param int|string|false $a Quantity value. + * @param int|string|false $b Quantity value. + * @return int|string|false Larger quantity value. + */ function wp_ini_quantity_max( $a, $b ) { return wp_ini_quantity_cmp( $a, $b ) >= 0 ? $a : $b; } +/** + * Returns smaller of two php.ini directive quantity values. + * + * Example: + * wp_ini_quantity_max( '256m', -1 ) === '256m' + * wp_ini_quantity_max( '64K', '64') === '64' + * wp_ini_qantity_max( 1000, 2000 ) === 1000 + * + * @param int|string|false $a Quantity value. + * @param int|string|false $b Quantity value. + * @return int|string|false Smaller quantity value. + */ function wp_ini_quantity_min( $a, $b ) { return wp_ini_quantity_cmp( $a, $b ) <= 0 ? $a : $b; } @@ -87,226 +111,227 @@ function wp_ini_quantity_cmp( $a, $b ) { return $a_scalar > $b_scalar ? 1 : -1; } -if ( ! function_exists( 'ini_parse_quantity' ) ): -/** - * Returns quantity represented by a php.ini directive's "byte size shorthand." - * - * php.ini directives may use a string representation of a number of bytes - * or a "shorthand" byte size to reference larger values. Multiple numeric - * php.ini directive use these shorthands even when they don't refer to bytes. - * - * Example: - * - * ini_parse_quantity( "1m" ) == 1048576 - * ini_parse_quantity( "2K" ) == 2048 // 2 * 1024 - * ini_parse_quantity( "0.5g" ) == 0 - * ini_parse_quantity( "14.6e-13g" ) == 15032385536 // 14 * 1024^3 - * ini_parse_quantity( "-813k" ) == 0; - * ini_parse_quantity( "boat" ) == 0; - * - * // This gives an answer, but it's _wrong_ because - * // the underlying mechanism in PHP overflowed and - * // the real return value depends on whether PHP - * // was built with 64-bit support. - * ini_parse_quantity( "9223372036854775807g" ) == ?? - * - * Notes: - * - Suffixes are specifically _the last character_ and case-insensitive. - * - Suffixes k/m/g intentionally report powers of 1024 to agree with PHP. - * - This function does not fail on invalid input; it returns `0` in such cses. - * - As noted in the PHP documentation, overflow behavior is unspecified and - * platform-dependant. Values that trigger overflow are likely wrong - * - * @since 6.1. - * - * @link https://www.php.net/manual/en/function.ini-get.php - * @link https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes - - * @param string $value Numeric string value possibly in "shorthand notation." - * @return int Parsed numeric value represented by given string. - */ -function ini_parse_quantity( $value ) { +if ( ! function_exists( 'ini_parse_quantity' ) ) : /** - * Number of bytes in input string; because we're only assessing 7-bit - * ASCII/Unicode characters we can safely count bytes vs. needing to - * worry about code units, code points, or grapheme clusters. + * Returns quantity represented by a php.ini directive's "byte size shorthand." * - * @var int - */ - $strlen = strlen( $value ); + * php.ini directives may use a string representation of a number of bytes + * or a "shorthand" byte size to reference larger values. Multiple numeric + * php.ini directive use these shorthands even when they don't refer to bytes. + * + * Example: + * + * ini_parse_quantity( "1m" ) == 1048576 + * ini_parse_quantity( "2K" ) == 2048 // 2 * 1024 + * ini_parse_quantity( "0.5g" ) == 0 + * ini_parse_quantity( "14.6e-13g" ) == 15032385536 // 14 * 1024^3 + * ini_parse_quantity( "-813k" ) == 0; + * ini_parse_quantity( "boat" ) == 0; + * + * // This gives an answer, but it's _wrong_ because + * // the underlying mechanism in PHP overflowed and + * // the real return value depends on whether PHP + * // was built with 64-bit support. + * ini_parse_quantity( "9223372036854775807g" ) == ?? + * + * Notes: + * - Suffixes are specifically _the last character_ and case-insensitive. + * - Suffixes k/m/g intentionally report powers of 1024 to agree with PHP. + * - This function does not fail on invalid input; it returns `0` in such cses. + * - As noted in the PHP documentation, overflow behavior is unspecified and + * platform-dependant. Values that trigger overflow are likely wrong + * + * @since 6.1. + * + * @link https://www.php.net/manual/en/function.ini-get.php + * @link https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes - /** @var int|float Numeric quantity represented by value string. */ - $scalar = 0; + * @param string $value Numeric string value possibly in "shorthand notation." + * @return int Parsed numeric value represented by given string. + */ + function ini_parse_quantity( $value ) { + /** + * Number of bytes in input string; because we're only assessing 7-bit + * ASCII/Unicode characters we can safely count bytes vs. needing to + * worry about code units, code points, or grapheme clusters. + * + * @var int + */ + $strlen = strlen( $value ); - /** @var int Sign of numeric quantity, either positive (1) or negative (-1). */ - $sign = 1; + /** @var int|float Numeric quantity represented by value string. */ + $scalar = 0; - /** - * Numeric base of digits determined by string prefix (e.g. "0x" or "0"). - * Must be 8 for octal, 10 for decimal, or 16 for hexadecimal. - * - * @var int - */ - $base = 10; + /** @var int Sign of numeric quantity, either positive (1) or negative (-1). */ + $sign = 1; - /** @var int Index into input string as we walk through it and analyze each character. */ - $i = 0; + /** + * Numeric base of digits determined by string prefix (e.g. "0x" or "0"). + * Must be 8 for octal, 10 for decimal, or 16 for hexadecimal. + * + * @var int + */ + $base = 10; - /* - * Trim leading whitespace. - * - * We could also do this with `ltrim()` but that adds a needless - * string copy and makes it appear like we could add `+` to the - * list of values to trim, which we cannot, because that - * results in the wrong parse for strings with multiple - * `+` or `-` characters in a row. - */ - for ( ; $i < $strlen; $i++ ) { - $c = $value[ $i ]; + /** @var int Index into input string as we walk through it and analyze each character. */ + $i = 0; - if ( ' ' === $c || "\t" === $c || "\r" === $c || "\v" === $c || "\f" === $c ) { - continue; - } + /* + * Trim leading whitespace. + * + * We could also do this with `ltrim()` but that adds a needless + * string copy and makes it appear like we could add `+` to the + * list of values to trim, which we cannot, because that + * results in the wrong parse for strings with multiple + * `+` or `-` characters in a row. + */ + for ( ; $i < $strlen; $i++ ) { + $c = $value[ $i ]; - break; - } + if ( ' ' === $c || "\t" === $c || "\r" === $c || "\v" === $c || "\f" === $c ) { + continue; + } - // Handle optional sign indicator. - switch ( $value[ $i ] ) { - case '+': - $i++; break; + } - case '-': - $sign = -1; - $i++; - break; - } + // Handle optional sign indicator. + switch ( $value[ $i ] ) { + case '+': + $i++; + break; - // Determine base for digit conversion, if not decimal. - $base_a = $i < $strlen ? $value[ $i ] : ''; - $base_b = $i + 1 < $strlen ? $value[ $i + 1 ] : ''; + case '-': + $sign = -1; + $i++; + break; + } - if ( '0' === $base_a && ( 'x' === $base_b || 'X' === $base_b ) ) { - $base = 16; - $i += 2; - } else if ( '0' === $base_a && ctype_digit( $base_b ) ) { - $base = 8; - $i += 1; - } + // Determine base for digit conversion, if not decimal. + $base_a = $i < $strlen ? $value[ $i ] : ''; + $base_b = $i + 1 < $strlen ? $value[ $i + 1 ] : ''; - // Trim leading zeros. - for ( ; $i < $strlen; $i++ ) { - if ( '0' !== $value[ $i ] ) { - break; + if ( '0' === $base_a && ( 'x' === $base_b || 'X' === $base_b ) ) { + $base = 16; + $i += 2; + } else if ( '0' === $base_a && ctype_digit( $base_b ) ) { + $base = 8; + $i += 1; } - } - /** - * Numeric values for scanned digits. - * - * These are used to determine the decimal value the digit - * represents and whether it's an allowed character in - * the given base. It's allowed if its value is less - * than the base: e.g. '7' is allowed in octal (base 8) - * but '8' and '9' aren't because they are greater than 8. - * - * @var array - */ - $digits = array( - '0' => 0, - '1' => 1, - '2' => 2, - '3' => 3, - '4' => 4, - '5' => 5, - '6' => 6, - '7' => 7, - '8' => 8, - '9' => 9, - 'A' => 10, - 'a' => 10, - 'B' => 11, - 'b' => 11, - 'C' => 12, - 'c' => 12, - 'D' => 13, - 'd' => 13, - 'E' => 14, - 'e' => 14, - 'F' => 15, - 'f' => 15, - ); - - // Build the scalar value by consuming the next sequence of contiguous digits. - for ( ; $i < $strlen; $i++ ) { - $c = $value[ $i ]; + // Trim leading zeros. + for ( ; $i < $strlen; $i++ ) { + if ( '0' !== $value[ $i ] ) { + break; + } + } - /* - * Only digits recognized in this base system can be used. - * Once we find an unrecognized digit we abort and move - * on to the next step in parsing the size suffix. + /** + * Numeric values for scanned digits. + * + * These are used to determine the decimal value the digit + * represents and whether it's an allowed character in + * the given base. It's allowed if its value is less + * than the base: e.g. '7' is allowed in octal (base 8) + * but '8' and '9' aren't because they are greater than 8. + * + * @var array */ - if ( ! isset( $digits[ $c ] ) || $digits[ $c ] >= $base ) { - break; + $digits = array( + '0' => 0, + '1' => 1, + '2' => 2, + '3' => 3, + '4' => 4, + '5' => 5, + '6' => 6, + '7' => 7, + '8' => 8, + '9' => 9, + 'A' => 10, + 'a' => 10, + 'B' => 11, + 'b' => 11, + 'C' => 12, + 'c' => 12, + 'D' => 13, + 'd' => 13, + 'E' => 14, + 'e' => 14, + 'F' => 15, + 'f' => 15, + ); + + // Build the scalar value by consuming the next sequence of contiguous digits. + for ( ; $i < $strlen; $i++ ) { + $c = $value[ $i ]; + + /* + * Only digits recognized in this base system can be used. + * Once we find an unrecognized digit we abort and move + * on to the next step in parsing the size suffix. + */ + if ( ! isset( $digits[ $c ] ) || $digits[ $c ] >= $base ) { + break; + } + + /* + * This is the step that computes our integer as we see new digits. + * + * Example: + * 4 = (0 * 10) + 4 + * 45 = ((0 * 10 + 4) * 10) + 5 + * 458 = ((0 * 10 + 4) * 10 + 5) * 10 + 8 + */ + $scalar = $scalar * $base + $digits[ $c ]; + + // Stop processing if we're already at the maximum magnitude for the sign. + if ( + ( $sign > 0 && $scalar > PHP_INT_MAX ) || + ( $sign < 0 && $scalar > -PHP_INT_MIN ) + ) { + break; + } + } + + // Clamp the parsed digits to an integer value as PHP does internally. + if ( $sign > 0 && $scalar >= PHP_INT_MAX ) { + $scalar = PHP_INT_MAX; + } else if ( $sign < 0 && $scalar >= -PHP_INT_MIN ) { + $scalar = PHP_INT_MIN; + } else if ( $sign < 0 ) { + $scalar = -$scalar; } /* - * This is the step that computes our integer as we see new digits. + * Do not use WP constants here (GB_IN_BYTES, MB_IN_BYTES, KB_IN_BYTES) + * since they are re-definable; PHP shorthand values are hard-coded + * in PHP itself and stay the same regardless of these constants. * - * Example: - * 4 = (0 * 10) + 4 - * 45 = ((0 * 10 + 4) * 10) + 5 - * 458 = ((0 * 10 + 4) * 10 + 5) * 10 + 8 + * Note that we can overflow here, as happens in PHP itself. + * Overflow results will likely not match PHP's value, but + * will likely break in most cases anyway and so leaving + * this loose is the best we can do until we can read these + * values directly from PHP. */ - $scalar = $scalar * $base + $digits[ $c ]; - - // Stop processing if we're already at the maximum magnitude for the sign. - if ( - ( $sign > 0 && $scalar > PHP_INT_MAX ) || - ( $sign < 0 && $scalar > -PHP_INT_MIN ) - ) { - break; + switch ( $value[ $strlen - 1 ] ) { + case 'g': + case 'G': + $scalar *= 1073741824; // 1024^3 + break; + + case 'm': + case 'M': + $scalar *= 1048576; // 1024^2 + break; + + case 'k': + case 'K': + $scalar *= 1024; + break; } - } - // Clamp the parsed digits to an integer value as PHP does internally. - if ( $sign > 0 && $scalar >= PHP_INT_MAX ) { - $scalar = PHP_INT_MAX; - } else if ( $sign < 0 && $scalar >= -PHP_INT_MIN ) { - $scalar = PHP_INT_MIN; - } else if ( $sign < 0 ) { - $scalar = -$scalar; + return (int) $scalar; } - /* - * Do not use WP constants here (GB_IN_BYTES, MB_IN_BYTES, KB_IN_BYTES) - * since they are re-definable; PHP shorthand values are hard-coded - * in PHP itself and stay the same regardless of these constants. - * - * Note that we can overflow here, as happens in PHP itself. - * Overflow results will likely not match PHP's value, but - * will likely break in most cases anyway and so leaving - * this loose is the best we can do until we can read these - * values directly from PHP. - */ - switch ( $value[ $strlen - 1 ] ) { - case 'g': - case 'G': - $scalar *= 1073741824; // 1024^3 - break; - - case 'm': - case 'M': - $scalar *= 1048576; // 1024^2 - break; - - case 'k': - case 'K': - $scalar *= 1024; - break; - } - - return (int) $scalar; -} endif; From 84739c9d4579a7fd9ba636a2cb36d9757864d84d Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Fri, 13 May 2022 12:47:01 -0700 Subject: [PATCH 14/20] fixup! Introduce comparison functions to avoid performing math on php.ini quantity values --- src/wp-includes/php-compat.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index 15449d72e9dac..c6ca507a22641 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -51,7 +51,7 @@ function wp_ini_parse_quantity( $value ) { * Example: * wp_ini_quantity_max( '256m', -1 ) === -1 * wp_ini_quantity_max( '64K', '64') === '64K' - * wp_ini_qantity_max( 1000, 2000 ) === 2000 + * wp_ini_quantity_max( 1000, 2000 ) === 2000 * * @param int|string|false $a Quantity value. * @param int|string|false $b Quantity value. @@ -65,9 +65,9 @@ function wp_ini_quantity_max( $a, $b ) { * Returns smaller of two php.ini directive quantity values. * * Example: - * wp_ini_quantity_max( '256m', -1 ) === '256m' - * wp_ini_quantity_max( '64K', '64') === '64' - * wp_ini_qantity_max( 1000, 2000 ) === 1000 + * wp_ini_quantity_min( '256m', -1 ) === '256m' + * wp_ini_quantity_min( '64K', '64') === '64' + * wp_ini_quantity_min( 1000, 2000 ) === 1000 * * @param int|string|false $a Quantity value. * @param int|string|false $b Quantity value. From 846049797231bb893c6eb0d347f36f4b39698e3b Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Fri, 13 May 2022 12:53:25 -0700 Subject: [PATCH 15/20] Fix example output --- src/wp-includes/php-compat.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index c6ca507a22641..5a48edbca687f 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -125,7 +125,7 @@ function wp_ini_quantity_cmp( $a, $b ) { * ini_parse_quantity( "2K" ) == 2048 // 2 * 1024 * ini_parse_quantity( "0.5g" ) == 0 * ini_parse_quantity( "14.6e-13g" ) == 15032385536 // 14 * 1024^3 - * ini_parse_quantity( "-813k" ) == 0; + * ini_parse_quantity( "-813k" ) == -832512; // -813 * 1024 * ini_parse_quantity( "boat" ) == 0; * * // This gives an answer, but it's _wrong_ because From adc2c0599ee731f154c9a4642fa87d710d828daf Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Sun, 11 Sep 2022 14:54:15 -0700 Subject: [PATCH 16/20] Refactor to use new LIMIT contstants, update comparison code --- src/wp-admin/includes/class-wp-debug-data.php | 2 +- .../includes/class-wp-site-health.php | 2 +- src/wp-includes/functions.php | 28 +++++---- src/wp-includes/media.php | 12 +++- src/wp-includes/php-compat.php | 57 ++++++++++++------- .../tests/php-compat/ini_parse_quantity.php | 28 ++++++++- 6 files changed, 87 insertions(+), 42 deletions(-) diff --git a/src/wp-admin/includes/class-wp-debug-data.php b/src/wp-admin/includes/class-wp-debug-data.php index 4d754a26457fb..0c3fa045dca2f 100644 --- a/src/wp-admin/includes/class-wp-debug-data.php +++ b/src/wp-admin/includes/class-wp-debug-data.php @@ -615,7 +615,7 @@ private static function get_wp_media(): array { $post_max_size = ini_get( 'post_max_size' ); $upload_max_filesize = ini_get( 'upload_max_filesize' ); $max_file_uploads = ini_get( 'max_file_uploads' ); - $effective = wp_ini_quantity_min( $post_max_size, $upload_max_filesize ); + $effective = wp_ini_lesser_quantity( $post_max_size, $upload_max_filesize ); // Add info in Media section. $fields['file_uploads'] = array( diff --git a/src/wp-admin/includes/class-wp-site-health.php b/src/wp-admin/includes/class-wp-site-health.php index a1d10555efe4a..ac7044b1fe1a6 100644 --- a/src/wp-admin/includes/class-wp-site-health.php +++ b/src/wp-admin/includes/class-wp-site-health.php @@ -2312,7 +2312,7 @@ public function get_test_file_uploads() { ); $result['status'] = 'recommended'; - if ( 0 === wp_ini_parse_quantity( $post_max_size ) ) { + if ( PHP_INI_LIMIT_MISSING === wp_ini_parse_quantity( $post_max_size ) ) { $result['description'] = sprintf( '

%s

', sprintf( diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 362c53a26c784..55b160221cc17 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -7847,7 +7847,8 @@ function wp_raise_memory_limit( $context = 'admin' ) { $current_limit = ini_get( 'memory_limit' ); - if ( 0 === wp_ini_parse_quantity( $current_limit ) ) { + // If we're already set to an unlimited value there's no higher limit to set. + if ( wp_ini_quantity_cmp( $current_limit, PHP_INI_LIMIT_UNLIMITED ) >= 0 ) { return false; } @@ -7927,22 +7928,19 @@ function wp_raise_memory_limit( $context = 'admin' ) { break; } - if ( - wp_ini_quantity_cmp( $filtered_limit, WP_MAX_MEMORY_LIMIT ) > 0 && - wp_ini_quantity_cmp( $filtered_limit, $current_limit ) > 0 - ) { - return false !== ini_set( 'memory_limit', $filtered_limit ) - ? $filtered_limit - : false; - } elseif ( - wp_ini_quantity_cmp( WP_MAX_MEMORY_LIMIT, $current_limit ) > 0 - ) { - return false !== ini_set( 'memory_limit', WP_MAX_MEMORY_LIMIT ) - ? WP_MAX_MEMORY_LIMIT - : false; + // Set the memory limit to the greatest of all the filtered value, the MAX limit, and the current limit. + $new_limit = wp_ini_greater_quantity( $current_limit, WP_MAX_MEMORY_LIMIT ); + $new_limit = wp_ini_greater_quantity( $filtered_limit, $new_limit ); + + // If we're already set at the greatest limit we don't need to change it. + if ( 0 === wp_ini_quantity_cmp( $new_limit, $current_limit ) ) { + return false; } - return false; + // Otherwise attempt to set the new limit and return the new value if it succeeded. + return false !== ini_set( 'memory_limit', $new_limit ) + ? $new_limit + : false; } /** diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 2507bc6e8753d..9ef3dcf65899f 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -4185,8 +4185,9 @@ function wp_expand_dimensions( $example_width, $example_height, $max_width, $max * @return int Allowed upload size. */ function wp_max_upload_size() { - $u_bytes = wp_ini_parse_quantity( ini_get( 'upload_max_filesize' ) ); - $p_bytes = wp_ini_parse_quantity( ini_get( 'post_max_size' ) ); + $upload_max_filesize = ini_get( 'upload_max_filesize' ); + $post_max_size = ini_get( 'post_max_size' ); + $max_upload = wp_ini_lesser_quantity( $upload_max_filesize, $post_max_size ); /** * Filters the maximum upload size allowed in php.ini. @@ -4197,7 +4198,12 @@ function wp_max_upload_size() { * @param int $u_bytes Maximum upload filesize in bytes. * @param int $p_bytes Maximum size of POST data in bytes. */ - return apply_filters( 'upload_size_limit', wp_ini_quantity_min( $u_bytes, $p_bytes ), $u_bytes, $p_bytes ); + return apply_filters( + 'upload_size_limit', + wp_ini_parse_quantity( $max_upload ), + wp_ini_parse_quantity( $upload_max_filesize ), + wp_ini_parse_quantity( $post_max_size ) + ); } /** diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index 5a48edbca687f..abcb895bc1f73 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -3,6 +3,8 @@ defined( 'IS_32_BIT_SYSTEM' ) || define( 'IS_32_BIT_SYSTEM', 2147483647 === PHP_INT_MAX ); defined( 'PHP_INT_MAX' ) || define( 'PHP_INT_MAX', IS_32_BIT_SYSTEM ? 2147483647 : 9223372036854775807 ); defined( 'PHP_INT_MIN' ) || define( 'PHP_INT_MIN', IS_32_BIT_SYSTEM ? -2147483648 : -9223372036854775808 ); +defined( 'PHP_INI_LIMIT_MISSING' ) || define( 'PHP_INI_LIMIT_MISSING', 0 ); +defined( 'PHP_INI_LIMIT_UNLIMITED' ) || define( 'PHP_INI_LIMIT_UNLIMITED', -1 ); /* * Returns byte size represented in a numeric php.ini directive. @@ -12,26 +14,25 @@ * It will return the value which PHP interprets from the "shorthand" * syntax, such as "128m" being 128 MiB and "128mb" being 128 B. * + * Since PHP 8.2 this may return inaccurate results if passed the value + * for the `memory_limit` INI directive as it uses the new unsigned + * parser. + * * @param false|int|string $value * @return int */ function wp_ini_parse_quantity( $value ) { // A missing value is an implicit lack of limit, thus we return `0`, meaning "no limit." if ( false === $value ) { - return 0; + return PHP_INI_LIMIT_MISSING; } /* * Directly return pre-parsed values so we can repeatedly call - * this without worrying if we already have for a given value. + * this without tracking if we've already parsed a given value. */ if ( is_int( $value ) ) { - /* - * All negative values imply no limit, so instead of - * returning the negative, return the real "no limit" - * value instead. - */ - return max( 0, $value ); + return $value; } /* @@ -39,7 +40,7 @@ function wp_ini_parse_quantity( $value ) { * no limit we could ascribe to this invalid value. */ if ( ! is_string( $value ) ) { - return 0; + return PHP_INI_LIMIT_MISSING; } return ini_parse_quantity( $value ); @@ -49,15 +50,15 @@ function wp_ini_parse_quantity( $value ) { * Returns larger of two php.ini directive quantity values. * * Example: - * wp_ini_quantity_max( '256m', -1 ) === -1 - * wp_ini_quantity_max( '64K', '64') === '64K' - * wp_ini_quantity_max( 1000, 2000 ) === 2000 + * wp_ini_greater_quantity( '256m', -1 ) === -1 + * wp_ini_greater_quantity( '64K', '64') === '64K' + * wp_ini_greater_quantity( 1000, 2000 ) === 2000 * * @param int|string|false $a Quantity value. * @param int|string|false $b Quantity value. * @return int|string|false Larger quantity value. */ -function wp_ini_quantity_max( $a, $b ) { +function wp_ini_greater_quantity( $a, $b ) { return wp_ini_quantity_cmp( $a, $b ) >= 0 ? $a : $b; } @@ -65,15 +66,15 @@ function wp_ini_quantity_max( $a, $b ) { * Returns smaller of two php.ini directive quantity values. * * Example: - * wp_ini_quantity_min( '256m', -1 ) === '256m' - * wp_ini_quantity_min( '64K', '64') === '64' - * wp_ini_quantity_min( 1000, 2000 ) === 1000 + * wp_ini_lesser_quantity( '256m', -1 ) === '256m' + * wp_ini_lesser_quantity( '64K', '64') === '64' + * wp_ini_lesser_quantity( 1000, 2000 ) === 1000 * * @param int|string|false $a Quantity value. * @param int|string|false $b Quantity value. * @return int|string|false Smaller quantity value. */ -function wp_ini_quantity_min( $a, $b ) { +function wp_ini_lesser_quantity( $a, $b ) { return wp_ini_quantity_cmp( $a, $b ) <= 0 ? $a : $b; } @@ -98,19 +99,30 @@ function wp_ini_quantity_cmp( $a, $b ) { return 0; } - // No limit on $a means it's at least as big as $b. - if ( 0 === $a_scalar ) { + // No limit on $a means $b provides our only known limit. + if ( PHP_INI_LIMIT_MISSING === $a_scalar ) { + return 1; + } + + // No limit on $b means $a provides our only known limit. + if ( PHP_INI_LIMIT_MISSING === $b_scalar ) { + return -1; + } + + // An explicit unlimited on $a is at least as great as all other limits. + if ( PHP_INI_LIMIT_UNLIMITED === $a_scalar ) { return 1; } - // Any limit on $a means it's smaller than a no-limit $b. - if ( 0 === $b_scalar ) { + // An explicit unlimited on $b is at least as great as all other limits. + if ( PHP_INI_LIMIT_UNLIMITED === $b_scalar ) { return -1; } return $a_scalar > $b_scalar ? 1 : -1; } +// ini_parse_quantity added to PHP in PHP 8.2 if ( ! function_exists( 'ini_parse_quantity' ) ) : /** * Returns quantity represented by a php.ini directive's "byte size shorthand." @@ -140,6 +152,9 @@ function wp_ini_quantity_cmp( $a, $b ) { * - This function does not fail on invalid input; it returns `0` in such cses. * - As noted in the PHP documentation, overflow behavior is unspecified and * platform-dependant. Values that trigger overflow are likely wrong + * - In PHP 8.2+ this function may return an invalid count for shorthand values + * parsed with the new unsigned parser. Currently only affects "memory_limit" + * and only when the value overflows an unsigned integer on the platform. * * @since 6.1. * diff --git a/tests/phpunit/tests/php-compat/ini_parse_quantity.php b/tests/phpunit/tests/php-compat/ini_parse_quantity.php index 02bae79393ff8..2bc8fbcfb2193 100644 --- a/tests/phpunit/tests/php-compat/ini_parse_quantity.php +++ b/tests/phpunit/tests/php-compat/ini_parse_quantity.php @@ -12,7 +12,33 @@ public function test_unset_limit_is_no_limit() { } public function test_absent_limit_is_no_limit() { - $this->assertEquals( 0, wp_ini_parse_quantity( -1 ) ); + $this->assertEquals( 0, wp_ini_parse_quantity( '' ) ); + } + + public function test_unlimited_is_unlimited() { + $this->assertEquals( -1, wp_ini_parse_quantity( '-1' ) ); + } + + public function test_unlimited_is_greater_than_missing_limit() { + $this->assertEqual( -1, wp_ini_greater_quantity( '', '-1' ) ); + } + + public function test_missing_limit_is_lesser_than_unlimited() { + $this->assertEqual( '', wp_ini_lesser_quantity( '', '-1' ) ); + } + + public function test_unlimited_is_greater_than_hard_limit() { + $this->assertEqual( 1, wp_ini_quantity_cmp( -1, 1348 ) ); + $this->assertEqual( 1, wp_ini_quantity_cmp( -1, '1348g' ) ); + } + + public function test_missing_limit_is_lesser_than_hard_limit() { + $this->assertEqual( -1, wp_ini_quantity_cmp( '', 1348 ) ); + $this->assertEqual( -1, wp_ini_quantity_cmp( '', '1348g' ) ); + $this->assertEqual( -1, wp_ini_quantity_cmp( 0, 1348 ) ); + $this->assertEqual( -1, wp_ini_quantity_cmp( 0, '1348g' ) ); + $this->assertEqual( -1, wp_ini_quantity_cmp( false, 1348 ) ); + $this->assertEqual( -1, wp_ini_quantity_cmp( false, '1348g' ) ); } public function test_invalid_data_is_no_limit() { From 62655879f1075475f79854a8b3bac7899e3bd47c Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Wed, 14 Sep 2022 15:54:55 -0700 Subject: [PATCH 17/20] Remove distinction between "no limit" and "unlimited" - it doesn't exist. --- .../includes/class-wp-site-health.php | 2 +- src/wp-includes/functions.php | 2 +- src/wp-includes/php-compat.php | 24 +++++------------- .../tests/php-compat/ini_parse_quantity.php | 25 ++++++++----------- 4 files changed, 19 insertions(+), 34 deletions(-) diff --git a/src/wp-admin/includes/class-wp-site-health.php b/src/wp-admin/includes/class-wp-site-health.php index ac7044b1fe1a6..3c1909fb144f5 100644 --- a/src/wp-admin/includes/class-wp-site-health.php +++ b/src/wp-admin/includes/class-wp-site-health.php @@ -2312,7 +2312,7 @@ public function get_test_file_uploads() { ); $result['status'] = 'recommended'; - if ( PHP_INI_LIMIT_MISSING === wp_ini_parse_quantity( $post_max_size ) ) { + if ( wp_ini_parse_quantity( $post_max_size ) <= 0 ) { $result['description'] = sprintf( '

%s

', sprintf( diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 55b160221cc17..62d1f1183a619 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -7848,7 +7848,7 @@ function wp_raise_memory_limit( $context = 'admin' ) { $current_limit = ini_get( 'memory_limit' ); // If we're already set to an unlimited value there's no higher limit to set. - if ( wp_ini_quantity_cmp( $current_limit, PHP_INI_LIMIT_UNLIMITED ) >= 0 ) { + if ( wp_ini_parse_quantity( $current_limit ) <= 0 ) { return false; } diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index abcb895bc1f73..584bfa63a01ff 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -3,8 +3,6 @@ defined( 'IS_32_BIT_SYSTEM' ) || define( 'IS_32_BIT_SYSTEM', 2147483647 === PHP_INT_MAX ); defined( 'PHP_INT_MAX' ) || define( 'PHP_INT_MAX', IS_32_BIT_SYSTEM ? 2147483647 : 9223372036854775807 ); defined( 'PHP_INT_MIN' ) || define( 'PHP_INT_MIN', IS_32_BIT_SYSTEM ? -2147483648 : -9223372036854775808 ); -defined( 'PHP_INI_LIMIT_MISSING' ) || define( 'PHP_INI_LIMIT_MISSING', 0 ); -defined( 'PHP_INI_LIMIT_UNLIMITED' ) || define( 'PHP_INI_LIMIT_UNLIMITED', -1 ); /* * Returns byte size represented in a numeric php.ini directive. @@ -24,7 +22,7 @@ function wp_ini_parse_quantity( $value ) { // A missing value is an implicit lack of limit, thus we return `0`, meaning "no limit." if ( false === $value ) { - return PHP_INI_LIMIT_MISSING; + return 0; } /* @@ -40,7 +38,7 @@ function wp_ini_parse_quantity( $value ) { * no limit we could ascribe to this invalid value. */ if ( ! is_string( $value ) ) { - return PHP_INI_LIMIT_MISSING; + return 0; } return ini_parse_quantity( $value ); @@ -99,23 +97,13 @@ function wp_ini_quantity_cmp( $a, $b ) { return 0; } - // No limit on $a means $b provides our only known limit. - if ( PHP_INI_LIMIT_MISSING === $a_scalar ) { - return 1; - } - - // No limit on $b means $a provides our only known limit. - if ( PHP_INI_LIMIT_MISSING === $b_scalar ) { - return -1; - } - - // An explicit unlimited on $a is at least as great as all other limits. - if ( PHP_INI_LIMIT_UNLIMITED === $a_scalar ) { + // No limit on $a means it's at least as large as any $b value. + if ( $a_scalar <= 0 ) { return 1; } - // An explicit unlimited on $b is at least as great as all other limits. - if ( PHP_INI_LIMIT_UNLIMITED === $b_scalar ) { + // No limit on $b means it's at least as large as any $a value. + if ( $b_scalar <= 0 ) { return -1; } diff --git a/tests/phpunit/tests/php-compat/ini_parse_quantity.php b/tests/phpunit/tests/php-compat/ini_parse_quantity.php index 2bc8fbcfb2193..5f6233615c466 100644 --- a/tests/phpunit/tests/php-compat/ini_parse_quantity.php +++ b/tests/phpunit/tests/php-compat/ini_parse_quantity.php @@ -19,26 +19,22 @@ public function test_unlimited_is_unlimited() { $this->assertEquals( -1, wp_ini_parse_quantity( '-1' ) ); } - public function test_unlimited_is_greater_than_missing_limit() { - $this->assertEqual( -1, wp_ini_greater_quantity( '', '-1' ) ); - } - - public function test_missing_limit_is_lesser_than_unlimited() { + public function test_unlimited_is_same_as_missing_limit() { + $this->assertEqual( '', wp_ini_greater_quantity( '', '-1' ) ); + $this->assertEqual( '-1', wp_ini_greater_quantity( '-1', '' ) ); $this->assertEqual( '', wp_ini_lesser_quantity( '', '-1' ) ); + $this->assertEqual( '-1', wp_ini_lesser_quantity( '-1', '' ) ); } public function test_unlimited_is_greater_than_hard_limit() { $this->assertEqual( 1, wp_ini_quantity_cmp( -1, 1348 ) ); $this->assertEqual( 1, wp_ini_quantity_cmp( -1, '1348g' ) ); - } - - public function test_missing_limit_is_lesser_than_hard_limit() { - $this->assertEqual( -1, wp_ini_quantity_cmp( '', 1348 ) ); - $this->assertEqual( -1, wp_ini_quantity_cmp( '', '1348g' ) ); - $this->assertEqual( -1, wp_ini_quantity_cmp( 0, 1348 ) ); - $this->assertEqual( -1, wp_ini_quantity_cmp( 0, '1348g' ) ); - $this->assertEqual( -1, wp_ini_quantity_cmp( false, 1348 ) ); - $this->assertEqual( -1, wp_ini_quantity_cmp( false, '1348g' ) ); + $this->assertEqual( 1, wp_ini_quantity_cmp( '', 1348 ) ); + $this->assertEqual( 1, wp_ini_quantity_cmp( '', '1348g' ) ); + $this->assertEqual( 1, wp_ini_quantity_cmp( 0, 1348 ) ); + $this->assertEqual( 1, wp_ini_quantity_cmp( 0, '1348g' ) ); + $this->assertEqual( 1, wp_ini_quantity_cmp( false, 1348 ) ); + $this->assertEqual( 1, wp_ini_quantity_cmp( false, '1348g' ) ); } public function test_invalid_data_is_no_limit() { @@ -50,6 +46,7 @@ public function test_invalid_data_is_no_limit() { public function test_returns_already_parsed_values() { $this->assertEquals( 15, wp_ini_parse_quantity( 15 ) ); + $this->assertEquals( -1543, wp_ini_parse_quantity( -1543 ) ); } public function test_clamped_to_max_int_before_suffix() { From 3ee2b76aace4695e9c015303c2c5b1ef2d8014e9 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Tue, 13 Jan 2026 17:49:40 -0700 Subject: [PATCH 18/20] Fixup: wrong merge resolution --- src/wp-includes/default-constants.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wp-includes/default-constants.php b/src/wp-includes/default-constants.php index 9918012a184f7..7139312c93d3a 100644 --- a/src/wp-includes/default-constants.php +++ b/src/wp-includes/default-constants.php @@ -57,6 +57,8 @@ function wp_initial_constants() { define( 'WP_MAX_MEMORY_LIMIT', $current_limit ); } elseif ( wp_ini_quantity_cmp( $current_limit, '256M' ) > 0 ) { define( 'WP_MAX_MEMORY_LIMIT', $current_limit ); + } elseif ( wp_ini_quantity_cmp( WP_MEMORY_LIMIT, '256M' ) > 0 ) { + define( 'WP_MAX_MEMORY_LIMIT', WP_MEMORY_LIMIT ); } else { define( 'WP_MAX_MEMORY_LIMIT', '256M' ); } From 5f569ded0935b274680536b8229ee41523baea2c Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Tue, 13 Jan 2026 18:37:07 -0700 Subject: [PATCH 19/20] Updates --- src/wp-includes/php-compat.php | 413 ++++++++++++++++----------------- 1 file changed, 202 insertions(+), 211 deletions(-) diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index 584bfa63a01ff..b92fa1ca8d847 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -1,20 +1,18 @@ $b_scalar ? 1 : -1; } -// ini_parse_quantity added to PHP in PHP 8.2 -if ( ! function_exists( 'ini_parse_quantity' ) ) : - /** - * Returns quantity represented by a php.ini directive's "byte size shorthand." - * - * php.ini directives may use a string representation of a number of bytes - * or a "shorthand" byte size to reference larger values. Multiple numeric - * php.ini directive use these shorthands even when they don't refer to bytes. - * - * Example: - * - * ini_parse_quantity( "1m" ) == 1048576 - * ini_parse_quantity( "2K" ) == 2048 // 2 * 1024 - * ini_parse_quantity( "0.5g" ) == 0 - * ini_parse_quantity( "14.6e-13g" ) == 15032385536 // 14 * 1024^3 - * ini_parse_quantity( "-813k" ) == -832512; // -813 * 1024 - * ini_parse_quantity( "boat" ) == 0; - * - * // This gives an answer, but it's _wrong_ because - * // the underlying mechanism in PHP overflowed and - * // the real return value depends on whether PHP - * // was built with 64-bit support. - * ini_parse_quantity( "9223372036854775807g" ) == ?? - * - * Notes: - * - Suffixes are specifically _the last character_ and case-insensitive. - * - Suffixes k/m/g intentionally report powers of 1024 to agree with PHP. - * - This function does not fail on invalid input; it returns `0` in such cses. - * - As noted in the PHP documentation, overflow behavior is unspecified and - * platform-dependant. Values that trigger overflow are likely wrong - * - In PHP 8.2+ this function may return an invalid count for shorthand values - * parsed with the new unsigned parser. Currently only affects "memory_limit" - * and only when the value overflows an unsigned integer on the platform. - * - * @since 6.1. - * - * @link https://www.php.net/manual/en/function.ini-get.php - * @link https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes +/** + * Returns quantity represented by a php.ini directive's "byte size shorthand." + * + * php.ini directives may use a string representation of a number of bytes + * or a "shorthand" byte size to reference larger values. Multiple numeric + * php.ini directive use these shorthands even when they don't refer to bytes. + * + * Example: + * + * ini_parse_quantity( "1m" ) == 1048576 + * ini_parse_quantity( "2K" ) == 2048 // 2 * 1024 + * ini_parse_quantity( "0.5g" ) == 0 + * ini_parse_quantity( "14.6e-13g" ) == 15032385536 // 14 * 1024^3 + * ini_parse_quantity( "-813k" ) == -832512; // -813 * 1024 + * ini_parse_quantity( "boat" ) == 0; + * + * // This gives an answer, but it's _wrong_ because + * // the underlying mechanism in PHP overflowed and + * // the real return value depends on whether PHP + * // was built with 64-bit support. + * ini_parse_quantity( "9223372036854775807g" ) == ?? + * + * Notes: + * - Suffixes are specifically _the last character_ and case-insensitive. + * - Suffixes k/m/g intentionally report powers of 1024 to agree with PHP. + * - This function does not fail on invalid input; it returns `0` in such cses. + * - As noted in the PHP documentation, overflow behavior is unspecified and + * platform-dependant. Values that trigger overflow are likely wrong + * - In PHP 8.2+ this function may return an invalid count for shorthand values + * parsed with the new unsigned parser. Currently only affects "memory_limit" + * and only when the value overflows an unsigned integer on the platform. + * + * @since 7.0.0 + * + * @link https://www.php.net/manual/en/function.ini-get.php + * @link https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes - * @param string $value Numeric string value possibly in "shorthand notation." - * @return int Parsed numeric value represented by given string. + * @param string $value Numeric string value possibly in "shorthand notation." + * @return int Parsed numeric value represented by given string. + */ +function ini_parse_quantity_fallback( $value ) { + /** + * Number of bytes in input string; because we're only assessing 7-bit + * ASCII/Unicode characters we can safely count bytes vs. needing to + * worry about code units, code points, or grapheme clusters. */ - function ini_parse_quantity( $value ) { - /** - * Number of bytes in input string; because we're only assessing 7-bit - * ASCII/Unicode characters we can safely count bytes vs. needing to - * worry about code units, code points, or grapheme clusters. - * - * @var int - */ - $strlen = strlen( $value ); + $end = strlen( $value ); - /** @var int|float Numeric quantity represented by value string. */ - $scalar = 0; + /** @var int|float $scalar Numeric quantity represented by value string. */ + $scalar = 0; - /** @var int Sign of numeric quantity, either positive (1) or negative (-1). */ - $sign = 1; + /** Sign of numeric quantity, either positive (1) or negative (-1). */ + $sign = 1; - /** - * Numeric base of digits determined by string prefix (e.g. "0x" or "0"). - * Must be 8 for octal, 10 for decimal, or 16 for hexadecimal. - * - * @var int - */ - $base = 10; + /** + * Numeric base of digits determined by string prefix (e.g. "0x" or "0"). + * Must be 8 for octal, 10 for decimal, or 16 for hexadecimal. + */ + $base = 10; - /** @var int Index into input string as we walk through it and analyze each character. */ - $i = 0; + /** Byte index into input being processed. */ + $at = 0; - /* - * Trim leading whitespace. - * - * We could also do this with `ltrim()` but that adds a needless - * string copy and makes it appear like we could add `+` to the - * list of values to trim, which we cannot, because that - * results in the wrong parse for strings with multiple - * `+` or `-` characters in a row. - */ - for ( ; $i < $strlen; $i++ ) { - $c = $value[ $i ]; + // Trim leading whitespace from the value. + $at += strspn( $value, " \t\r\v\f", $at ); + if ( $at >= $end ) { + return $scalar; + } - if ( ' ' === $c || "\t" === $c || "\r" === $c || "\v" === $c || "\f" === $c ) { - continue; - } + // Handle optional sign indicator. + switch ( $value[ $at ] ) { + case '+': + $at++; + break; + case '-': + $sign = -1; + $at++; break; - } + } - // Handle optional sign indicator. - switch ( $value[ $i ] ) { - case '+': - $i++; - break; + // Determine base for digit conversion, if not decimal. + $base_a = $value[ $at ] ?? ''; + $base_b = $value[ $at + 1 ] ?? ''; - case '-': - $sign = -1; - $i++; - break; - } + if ( '0' === $base_a && ( 'x' === $base_b || 'X' === $base_b ) ) { + $base = 16; + $at += 2; + } else if ( '0' === $base_a && '0' <= $base_b && $base_b <= '9' ) { + $base = 8; + $at += 1; + } - // Determine base for digit conversion, if not decimal. - $base_a = $i < $strlen ? $value[ $i ] : ''; - $base_b = $i + 1 < $strlen ? $value[ $i + 1 ] : ''; + // Trim leading zeros from the amount. + $at += strspn( $value, '0', $at ); - if ( '0' === $base_a && ( 'x' === $base_b || 'X' === $base_b ) ) { - $base = 16; - $i += 2; - } else if ( '0' === $base_a && ctype_digit( $base_b ) ) { - $base = 8; - $i += 1; - } + /** + * Numeric values for scanned digits. + * + * These are used to determine the decimal value the digit + * represents and whether it's an allowed character in + * the given base. It's allowed if its value is less + * than the base: e.g. '7' is allowed in octal (base 8) + * but '8' and '9' aren't because they are greater than 8. + * + * @var array $digits + */ + $digits = array( + '0' => 0, + '1' => 1, + '2' => 2, + '3' => 3, + '4' => 4, + '5' => 5, + '6' => 6, + '7' => 7, + '8' => 8, + '9' => 9, + 'A' => 10, + 'a' => 10, + 'B' => 11, + 'b' => 11, + 'C' => 12, + 'c' => 12, + 'D' => 13, + 'd' => 13, + 'E' => 14, + 'e' => 14, + 'F' => 15, + 'f' => 15, + ); - // Trim leading zeros. - for ( ; $i < $strlen; $i++ ) { - if ( '0' !== $value[ $i ] ) { - break; - } - } + /* + * Build the scalar value by consuming the next sequence of contiguous digits. + * It looks like this could be replaced with native functions to parse digits, + * but the behavior mimics peculiarities internal to PHP when parsing overflowed + * digits, inasmuch as is possible from user-space PHP code. + */ + for ( ; $at < $end; $at++ ) { + $c = $value[ $at ]; - /** - * Numeric values for scanned digits. - * - * These are used to determine the decimal value the digit - * represents and whether it's an allowed character in - * the given base. It's allowed if its value is less - * than the base: e.g. '7' is allowed in octal (base 8) - * but '8' and '9' aren't because they are greater than 8. - * - * @var array + /* + * Only digits recognized in this base system can be used. + * Once any unrecognized digits are found, abort and move + * on to the next step in parsing the size suffix. */ - $digits = array( - '0' => 0, - '1' => 1, - '2' => 2, - '3' => 3, - '4' => 4, - '5' => 5, - '6' => 6, - '7' => 7, - '8' => 8, - '9' => 9, - 'A' => 10, - 'a' => 10, - 'B' => 11, - 'b' => 11, - 'C' => 12, - 'c' => 12, - 'D' => 13, - 'd' => 13, - 'E' => 14, - 'e' => 14, - 'F' => 15, - 'f' => 15, - ); - - // Build the scalar value by consuming the next sequence of contiguous digits. - for ( ; $i < $strlen; $i++ ) { - $c = $value[ $i ]; - - /* - * Only digits recognized in this base system can be used. - * Once we find an unrecognized digit we abort and move - * on to the next step in parsing the size suffix. - */ - if ( ! isset( $digits[ $c ] ) || $digits[ $c ] >= $base ) { - break; - } - - /* - * This is the step that computes our integer as we see new digits. - * - * Example: - * 4 = (0 * 10) + 4 - * 45 = ((0 * 10 + 4) * 10) + 5 - * 458 = ((0 * 10 + 4) * 10 + 5) * 10 + 8 - */ - $scalar = $scalar * $base + $digits[ $c ]; - - // Stop processing if we're already at the maximum magnitude for the sign. - if ( - ( $sign > 0 && $scalar > PHP_INT_MAX ) || - ( $sign < 0 && $scalar > -PHP_INT_MIN ) - ) { - break; - } - } - - // Clamp the parsed digits to an integer value as PHP does internally. - if ( $sign > 0 && $scalar >= PHP_INT_MAX ) { - $scalar = PHP_INT_MAX; - } else if ( $sign < 0 && $scalar >= -PHP_INT_MIN ) { - $scalar = PHP_INT_MIN; - } else if ( $sign < 0 ) { - $scalar = -$scalar; + if ( ! isset( $digits[ $c ] ) || $digits[ $c ] >= $base ) { + break; } /* - * Do not use WP constants here (GB_IN_BYTES, MB_IN_BYTES, KB_IN_BYTES) - * since they are re-definable; PHP shorthand values are hard-coded - * in PHP itself and stay the same regardless of these constants. + * This is the step that computes the integer as new digits arrive. * - * Note that we can overflow here, as happens in PHP itself. - * Overflow results will likely not match PHP's value, but - * will likely break in most cases anyway and so leaving - * this loose is the best we can do until we can read these - * values directly from PHP. + * Example: + * 4 = (0 * 10) + 4 + * 45 = ((0 * 10 + 4) * 10) + 5 + * 458 = ((0 * 10 + 4) * 10 + 5) * 10 + 8 + */ + $scalar = $scalar * $base + $digits[ $c ]; + + /* + * There’s nothing to do one having reached the maximum + * storable size, so bail. This comparison works because + * the int value is cast into a float once having crossed + * beyond the maximum integer. */ - switch ( $value[ $strlen - 1 ] ) { - case 'g': - case 'G': - $scalar *= 1073741824; // 1024^3 - break; - - case 'm': - case 'M': - $scalar *= 1048576; // 1024^2 - break; - - case 'k': - case 'K': - $scalar *= 1024; - break; + if ( + ( $sign > 0 && $scalar > PHP_INT_MAX ) || + ( $sign < 0 && $scalar > -PHP_INT_MIN ) + ) { + break; } + } - return (int) $scalar; + // Clamp the parsed digits to an integer value as PHP does internally. + if ( $sign > 0 && $scalar >= PHP_INT_MAX ) { + $scalar = PHP_INT_MAX; + } else if ( $sign < 0 && $scalar >= -PHP_INT_MIN ) { + $scalar = PHP_INT_MIN; + } else if ( $sign < 0 ) { + $scalar = -$scalar; } -endif; + // @todo Any values above these, when multiplied, will overflow. + $max_int_g = floor( PHP_INT_MAX / 1073741824 ); + $max_int_m = floor( PHP_INT_MAX / 1048576 ); + $max_int_k = floor( PHP_INT_MAX / 1024 ); + + /* + * Do not use WP constants here (GB_IN_BYTES, MB_IN_BYTES, KB_IN_BYTES) + * since they are re-definable; PHP shorthand values are hard-coded + * in PHP itself and stay the same regardless of these constants. + * + * Note that it’s possible to overflow here, as happens in PHP itself. + * Overflow results will likely not match PHP’s value, but will likely + * break in most cases anyway and so leaving this loose is the best + * that can be done without PHP reporting the internal values. + * + * @todo Is is possible to detect overflow here? + */ + switch ( $value[ $end - 1 ] ) { + case 'g': + case 'G': + $scalar *= 1073741824; // 1024^3 + break; + + case 'm': + case 'M': + $scalar *= 1048576; // 1024^2 + break; + + case 'k': + case 'K': + $scalar *= 1024; + break; + } + + return (int) $scalar; +} From bc40ee82b128d2f11a8ca2844124d1538a70e329 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Wed, 14 Jan 2026 11:50:27 -0700 Subject: [PATCH 20/20] Cleanup, simplify --- src/wp-includes/php-compat.php | 89 +++++++++------------------------- 1 file changed, 24 insertions(+), 65 deletions(-) diff --git a/src/wp-includes/php-compat.php b/src/wp-includes/php-compat.php index b92fa1ca8d847..7e4652003aa15 100644 --- a/src/wp-includes/php-compat.php +++ b/src/wp-includes/php-compat.php @@ -1,7 +1,5 @@ = $end ) { return $scalar; } // Handle optional sign indicator. - switch ( $value[ $at ] ) { + switch ( $shorthand[ $at ] ) { case '+': $at++; break; @@ -192,19 +157,19 @@ function ini_parse_quantity_fallback( $value ) { } // Determine base for digit conversion, if not decimal. - $base_a = $value[ $at ] ?? ''; - $base_b = $value[ $at + 1 ] ?? ''; + $base_a = $shorthand[ $at ] ?? ''; + $base_b = $shorthand[ $at + 1 ] ?? ''; if ( '0' === $base_a && ( 'x' === $base_b || 'X' === $base_b ) ) { $base = 16; - $at += 2; + $at += 2; } else if ( '0' === $base_a && '0' <= $base_b && $base_b <= '9' ) { $base = 8; - $at += 1; + $at += 1; } // Trim leading zeros from the amount. - $at += strspn( $value, '0', $at ); + $at += strspn( $shorthand, '0', $at ); /** * Numeric values for scanned digits. @@ -249,7 +214,7 @@ function ini_parse_quantity_fallback( $value ) { * digits, inasmuch as is possible from user-space PHP code. */ for ( ; $at < $end; $at++ ) { - $c = $value[ $at ]; + $c = $shorthand[ $at ]; /* * Only digits recognized in this base system can be used. @@ -293,24 +258,18 @@ function ini_parse_quantity_fallback( $value ) { $scalar = -$scalar; } - // @todo Any values above these, when multiplied, will overflow. - $max_int_g = floor( PHP_INT_MAX / 1073741824 ); - $max_int_m = floor( PHP_INT_MAX / 1048576 ); - $max_int_k = floor( PHP_INT_MAX / 1024 ); - /* * Do not use WP constants here (GB_IN_BYTES, MB_IN_BYTES, KB_IN_BYTES) * since they are re-definable; PHP shorthand values are hard-coded - * in PHP itself and stay the same regardless of these constants. + * in PHP itself and stay the same regardless of these constants. Also, + * this file loads before these constants are defined. * * Note that it’s possible to overflow here, as happens in PHP itself. * Overflow results will likely not match PHP’s value, but will likely * break in most cases anyway and so leaving this loose is the best * that can be done without PHP reporting the internal values. - * - * @todo Is is possible to detect overflow here? */ - switch ( $value[ $end - 1 ] ) { + switch ( $shorthand[ $end - 1 ] ) { case 'g': case 'G': $scalar *= 1073741824; // 1024^3