diff --git a/src/filesystem/Filesystem.php b/src/filesystem/Filesystem.php --- a/src/filesystem/Filesystem.php +++ b/src/filesystem/Filesystem.php @@ -546,6 +546,22 @@ * @param int Maximum value, inclusive. */ public static function readRandomInteger($min, $max) { + if (!is_int($min)) { + throw new Exception(pht('Minimum value must be an integer.')); + } + + if (!is_int($max)) { + throw new Exception(pht('Maximum value must be an integer.')); + } + + if ($min > $max) { + throw new Exception( + pht( + 'Minimum ("%d") must not be greater than maximum ("%d").', + $min, + $max)); + } + // Under PHP 7.2.0 and newer, we can just use "random_int()". This function // is intended to generate cryptographically usable entropy. if (function_exists('random_int')) { @@ -557,7 +573,17 @@ // like we're more likely to get that wrong than suffer a PRNG prediction // issue by falling back to "mt_rand()". - return mt_rand($min, $max); + if (($max - $min) > mt_getrandmax()) { + throw new Exception( + pht('mt_rand() range is smaller than the requested range.')); + } + + $result = mt_rand($min, $max); + if (!is_int($result)) { + throw new Exception(pht('Bad return value from mt_rand().')); + } + + return $result; } diff --git a/src/filesystem/__tests__/FilesystemTestCase.php b/src/filesystem/__tests__/FilesystemTestCase.php --- a/src/filesystem/__tests__/FilesystemTestCase.php +++ b/src/filesystem/__tests__/FilesystemTestCase.php @@ -175,4 +175,42 @@ } } + public function testRandomIntegers() { + $valid_ranges = array( + array(5, 5), + array(-1, 1), + array(0, 10000), + array(0, 999999999), + array(-65535, 65536), + ); + + foreach ($valid_ranges as $case) { + list($min, $max) = $case; + + $result = Filesystem::readRandomInteger($min, $max); + + $this->assertTrue($min <= $result, pht('%d <= %d', $min, $result)); + $this->assertTrue($max >= $result, pht('%d >= %d', $max, $result)); + } + + $invalid_ranges = array( + array('1', '2'), + array(1.0, 2.0), + array(5, 3), + ); + + foreach ($invalid_ranges as $case) { + list($min, $max) = $case; + + $caught = null; + try { + Filesystem::readRandomInteger($min, $max); + } catch (Exception $ex) { + $caught = $ex; + } + + $this->assertTrue($caught instanceof Exception); + } + } + }