diff --git a/lib/private/MemoryInfo.php b/lib/private/MemoryInfo.php index 9b5c709b4a94e..ba794d6d15970 100644 --- a/lib/private/MemoryInfo.php +++ b/lib/private/MemoryInfo.php @@ -3,14 +3,12 @@ declare(strict_types=1); /** - * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2018-2026 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC; -use OCP\Util; - /** * Helper class that covers memory info. */ @@ -18,9 +16,10 @@ class MemoryInfo { public const RECOMMENDED_MEMORY_LIMIT = 512 * 1024 * 1024; /** - * Tests if the memory limit is greater or equal the recommended value. + * Tests if the memory limit is compliant with the recommendation value. * * @return bool + * @throws \InvalidArgumentException (via $this->getMemoryLimit()) if the memory limit is misconfigured. */ public function isMemoryLimitSufficient(): bool { $memoryLimit = $this->getMemoryLimit(); @@ -28,47 +27,25 @@ public function isMemoryLimitSufficient(): bool { } /** - * Returns the php memory limit. - * - * @return int|float The memory limit in bytes. - */ - public function getMemoryLimit(): int|float { - $iniValue = trim(ini_get('memory_limit')); - if ($iniValue === '-1') { - return -1; - } elseif (is_numeric($iniValue)) { - return Util::numericToNumber($iniValue); - } else { - return $this->memoryLimitToBytes($iniValue); - } - } - - /** - * Converts the ini memory limit to bytes. + * Returns the interpreted (by PHP) memory limit in bytes. * - * @param string $memoryLimit The "memory_limit" ini value + * @return int The memory limit in bytes, or -1 if unlimited. + * @throws \InvalidArgumentException if the memory_limit value cannot be parsed. */ - private function memoryLimitToBytes(string $memoryLimit): int|float { - $last = strtolower(substr($memoryLimit, -1)); - $number = substr($memoryLimit, 0, -1); - if (is_numeric($number)) { - $memoryLimit = Util::numericToNumber($number); - } else { - throw new \InvalidArgumentException($number . ' is not a valid numeric string (in memory_limit ini directive)'); + public function getMemoryLimit(): int { + $iniValue = ini_get('memory_limit'); + + set_error_handler(function($errno, $errstr) { + throw new \ErrorException($errstr, 0, $errno); + }); + + try { + $bytes = ini_parse_quantity($iniValue); // can emit E_WARNING + return $bytes; + } catch (\ErrorException $e) { + throw new \InvalidArgumentException('Error parsing PHP memory_limit ini directive: ' . $e->getMessage()); + } finally { + restore_error_handler(); } - - // intended fall through - switch ($last) { - case 'g': - $memoryLimit *= 1024; - // no break - case 'm': - $memoryLimit *= 1024; - // no break - case 'k': - $memoryLimit *= 1024; - } - - return $memoryLimit; } } diff --git a/lib/public/Util.php b/lib/public/Util.php index 70a862880f19d..6532c518aba76 100644 --- a/lib/public/Util.php +++ b/lib/public/Util.php @@ -525,24 +525,40 @@ public static function recursiveArraySearch($haystack, $needle, $index = null) { } /** - * calculates the maximum upload size respecting system settings, free space and user quota + * Calculates the maximum allowed upload size for a directory, + * considering available free space/quota and PHP upload size configuration. * - * @param string $dir the current folder where the user currently operates - * @param int|float|null $free the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly - * @return int|float number of bytes representing + * @param string $dir Directory to check free space for. + * @param int|float|null $free Optional: bytes free on storage; retrieved if not provided. + * @return int|float Max upload size in bytes, or -1 if unlimited. * @since 5.0.0 */ public static function maxUploadFilesize(string $dir, int|float|null $free = null): int|float { if (is_null($free) || $free < 0) { $free = self::freeSpace($dir); } - return min($free, self::uploadLimit()); + $limit = self::uploadLimit(); + + if ($free === -1 && $limit === -1) { + return -1; + } + + // If only one is unlimited, return the finite value + if ($free === -1) { + return $limit; + } + if ($limit === -1) { + return $free; + } + // Both finite + return min($free, $limit); } /** - * Calculate free space left within user quota - * @param string $dir the current folder where the user currently operates - * @return int|float number of bytes representing + * Gets the available free space (respecting user quota) for the given directory. + * + * @param string $dir Directory to compute free space for. + * @return int|float Bytes free (0 or more), or INF for unlimited. * @since 7.0.0 */ public static function freeSpace(string $dir): int|float { @@ -556,22 +572,57 @@ public static function freeSpace(string $dir): int|float { } /** - * Calculate PHP upload limit + * Calculates the effective PHP upload limit in bytes, based on ini settings. * - * @return int|float number of bytes representing + * Returns the strictest limit from `upload_max_filesize` and `post_max_size`, + * treating 0 and -1 as unlimited. Returns -1 if both limits are unlimited. + * + * @return int Effective upload limit in bytes, or -1 if unlimited. + * @throws \InvalidArgumentException If unable to parse the ini values. * @since 7.0.0 */ - public static function uploadLimit(): int|float { - $ini = Server::get(IniGetWrapper::class); - $upload_max_filesize = self::computerFileSize($ini->get('upload_max_filesize')) ?: 0; - $post_max_size = self::computerFileSize($ini->get('post_max_size')) ?: 0; - if ($upload_max_filesize === 0 && $post_max_size === 0) { - return INF; - } elseif ($upload_max_filesize === 0 || $post_max_size === 0) { - return max($upload_max_filesize, $post_max_size); //only the non 0 value counts - } else { - return min($upload_max_filesize, $post_max_size); + public static function uploadLimit(): int { + $uploadMaxString = ini_get('upload_max_filesize'); + $postMaxString = ini_get('post_max_size'); + + set_error_handler(function($errno, $errstr) { + throw new \ErrorException($errstr, 0, $errno); + }); + + try { + $uploadMax = ini_parse_quantity($uploadMaxString); + $postMax = ini_parse_quantity($postMaxString); + } catch (\ErrorException $e) { + throw new \InvalidArgumentException( + 'Error parsing PHP upload_max_filesize or post_max_size ini directive: ' . $e->getMessage() + ); + } finally { + restore_error_handler(); + } + + // For these config parameters, both -1 and 0 mean unlimited in modern PHP, so normalize 0 to -1. + $uploadMax = ($uploadMax === 0) ? -1 : $uploadMax; + $postMax = ($postMax === 0) ? -1 : $postMax; + + if ($uploadMax === -1 && $postMax === -1) { + return -1; // unlimited + } + + if ($uploadMax > $postMax && $postMax !== -1) { + // Optional: Log a warning if upload_max_filesize exceeds post_max_size (or a setup check) + // Actual upload limit will be restricted by post_max_size + return $postMax; + } + + if ($uploadMax === -1) { + return $postMax; } + if ($postMax === -1) { + return $uploadMax; + } + + // Normal case: return the most restrictive (lowest) finite limit + return min($uploadMax, $postMax); } /** diff --git a/tests/lib/MemoryInfoTest.php b/tests/lib/MemoryInfoTest.php index 707b214912286..5b3adc9c969d4 100644 --- a/tests/lib/MemoryInfoTest.php +++ b/tests/lib/MemoryInfoTest.php @@ -32,12 +32,14 @@ public function restoreMemoryInfoIniSetting() { } public static function getMemoryLimitTestData(): array { + // On 32-bit, '2G' should overflow; on 64-bit, it'll be 2147483648 + $twoG = PHP_INT_SIZE === 4 ? -2147483648 : 2147483648; return [ 'unlimited' => ['-1', -1,], '524288000 bytes' => ['524288000', 524288000,], '500M' => ['500M', 524288000,], '512000K' => ['512000K', 524288000,], - '2G' => ['2G', 2147483648,], + '2G' => ['2G', $twoG,], ]; }