From e1f77941fd5f48b3684601dc173138d1d47bd696 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Wed, 9 Aug 2023 23:02:18 +1000 Subject: [PATCH] [10.x] Gracefully handle scientific notation (#48002) * Limit scientific notation exponent size * Naming * Flip guard * Lint --- .../Concerns/ValidatesAttributes.php | 32 ++++++++- src/Illuminate/Validation/Validator.php | 20 ++++++ tests/Validation/ValidationValidatorTest.php | 71 +++++++++++++++++++ 3 files changed, 122 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index 3c2f2bb6d12b..44545565c2ea 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -2352,7 +2352,7 @@ protected function getSize($attribute, $value) // is the size. If it is a file, we take kilobytes, and for a string the // entire length of the string will be considered the attribute size. if (is_numeric($value) && $hasNumeric) { - return $this->trim($value); + return $this->ensureExponentWithinAllowedRange($attribute, $this->trim($value)); } elseif (is_array($value)) { return count($value); } elseif ($value instanceof File) { @@ -2469,4 +2469,34 @@ protected function trim($value) { return is_string($value) ? trim($value) : $value; } + + /** + * Ensure the exponent is within the allowed range. + * + * @param string $attribute + * @param mixed $value + * @return mixed + */ + protected function ensureExponentWithinAllowedRange($attribute, $value) + { + $stringValue = (string) $value; + + if (! is_numeric($value) || ! Str::contains($stringValue, 'e', ignoreCase: true)) { + return $value; + } + + $scale = (int) (Str::contains($stringValue, 'e') + ? Str::after($stringValue, 'e') + : Str::after($stringValue, 'E')); + + $withinRange = ( + $this->ensureExponentWithinAllowedRangeUsing ?? fn ($scale) => $scale <= 1000 && $scale >= -1000 + )($scale, $attribute, $value); + + if (! $withinRange) { + throw new MathException('Scientific notation exponent outside of allowed range.'); + } + + return $value; + } } diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index 9cae4764343f..fdcf28fb37c8 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -295,6 +295,13 @@ class Validator implements ValidatorContract */ protected $exception = ValidationException::class; + /** + * The custom callback to determine if an exponent is within allowed range. + * + * @var callable|null + */ + protected $ensureExponentWithinAllowedRangeUsing; + /** * Create a new Validator instance. * @@ -1491,6 +1498,19 @@ public function setException($exception) return $this; } + /** + * Ensure exponent is within range with the given callback. + * + * @param callable(int $scale, string $attribute, mixed $value) $callback + * @return $this + */ + public function ensureExponentWithinAllowedRangeUsing($callback) + { + $this->ensureExponentWithinAllowedRangeUsing = $callback; + + return $this; + } + /** * Get the Translator implementation. * diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index b1e5da1315cb..2b5f735065c3 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -18,6 +18,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; use Illuminate\Support\Carbon; +use Illuminate\Support\Exceptions\MathException; use Illuminate\Translation\ArrayLoader; use Illuminate\Translation\Translator; use Illuminate\Validation\DatabasePresenceVerifierInterface; @@ -8767,6 +8768,76 @@ public function testItTrimsSpaceFromParameters() ], $validator->messages()->keys()); } + /** @dataProvider outsideRangeExponents */ + public function testItLimitsLengthOfScientificNotationExponent($value) + { + $trans = $this->getIlluminateArrayTranslator(); + $validator = new Validator($trans, ['foo' => $value], ['foo' => 'numeric|min:3']); + + $this->expectException(MathException::class); + $this->expectExceptionMessage('Scientific notation exponent outside of allowed range.'); + + $validator->passes(); + } + + public static function outsideRangeExponents() + { + return [ + ['1.0e+1001'], + ['1.0E+1001'], + ['1.0e1001'], + ['1.0E1001'], + ['1.0e-1001'], + ['1.0E-1001'], + ]; + } + + /** @dataProvider withinRangeExponents */ + public function testItAllowsScientificNotationWithinRange($value, $rule) + { + $trans = $this->getIlluminateArrayTranslator(); + $validator = new Validator($trans, ['foo' => $value], ['foo' => ['numeric', $rule]]); + + $this->assertTrue($validator->passes()); + } + + public static function withinRangeExponents() + { + return [ + ['1.0e+1000', 'min:3'], + ['1.0E+1000', 'min:3'], + ['1.0e1000', 'min:3'], + ['1.0E1000', 'min:3'], + ['1.0e-1000', 'max:3'], + ['1.0E-1000', 'max:3'], + ]; + } + + public function testItCanConfigureAllowedExponentRange() + { + $trans = $this->getIlluminateArrayTranslator(); + $validator = new Validator($trans, ['foo' => '1.0e-1000'], ['foo' => ['numeric', 'max:3']]); + $scale = $attribute = $value = null; + $withinRange = true; + + $validator->ensureExponentWithinAllowedRangeUsing(function () use (&$scale, &$attribute, &$value, &$withinRange) { + [$scale, $attribute, $value] = func_get_args(); + + return $withinRange; + }); + + $this->assertTrue($validator->passes()); + $this->assertSame(-1000, $scale); + $this->assertSame('foo', $attribute); + $this->assertSame('1.0e-1000', $value); + + $withinRange = false; + $this->expectException(MathException::class); + $this->expectExceptionMessage('Scientific notation exponent outside of allowed range.'); + + $validator->passes(); + } + protected function getTranslator() { return m::mock(TranslatorContract::class);