Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bf5134e

Browse files
mudassaralichouhannicolas-grekas
authored andcommitted
[HttpKernel] Validate typed request attribute values before calling controllers
When a route parameter is bound to a typed controller argument (int, float, bool, string, or \BackedEnum), invalid or out-of-range values now result in an HTTP error instead of triggering a TypeError.
1 parent 2363764 commit bf5134e

5 files changed

Lines changed: 123 additions & 11 deletions

File tree

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
8.1
55
---
66

7+
* Validate typed route parameters before calling controllers and return an HTTP error when an invalid value is provided
78
* Add `ControllerAttributeEvent` et al. to dispatch events named after controller attributes
89
* Add support for `UploadedFile` when using `MapRequestPayload`
910
* Add support for bundles as compiler pass

src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/BackedEnumValueResolver.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,16 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable
3535
return [];
3636
}
3737

38+
$name = $argument->getName();
39+
3840
// do not support if no value can be resolved at all
3941
// letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver be used
4042
// or \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error.
41-
if (!$request->attributes->has($argument->getName())) {
43+
if (!$request->attributes->has($name)) {
4244
return [];
4345
}
4446

45-
$value = $request->attributes->get($argument->getName());
46-
47+
$value = $request->attributes->get($name);
4748
if (null === $value) {
4849
return [null];
4950
}
@@ -52,17 +53,17 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable
5253
return [$value];
5354
}
5455

56+
/** @var class-string<\BackedEnum> $type */
57+
$type = $argument->getType();
58+
5559
if (!\is_int($value) && !\is_string($value)) {
56-
throw new \LogicException(\sprintf('Could not resolve the "%s $%s" controller argument: expecting an int or string, got "%s".', $argument->getType(), $argument->getName(), get_debug_type($value)));
60+
throw new NotFoundHttpException(\sprintf('Could not resolve the "%s $%s" controller argument: expecting an int or string, got "%s".', $type, $name, get_debug_type($value)));
5761
}
5862

59-
/** @var class-string<\BackedEnum> $enumType */
60-
$enumType = $argument->getType();
61-
6263
try {
63-
return [$enumType::from($value)];
64+
return [$type::from($value)];
6465
} catch (\ValueError|\TypeError $e) {
65-
throw new NotFoundHttpException(\sprintf('Could not resolve the "%s $%s" controller argument: ', $argument->getType(), $argument->getName()).$e->getMessage(), $e);
66+
throw new NotFoundHttpException(\sprintf('Could not resolve the "%s $%s" controller argument: ', $type, $name).$e->getMessage(), $e);
6667
}
6768
}
6869
}

src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\HttpFoundation\Request;
1515
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
1616
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
17+
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
1718

1819
/**
1920
* Yields a non-variadic argument's value from the request attributes.
@@ -24,6 +25,59 @@ final class RequestAttributeValueResolver implements ValueResolverInterface
2425
{
2526
public function resolve(Request $request, ArgumentMetadata $argument): array
2627
{
27-
return !$argument->isVariadic() && $request->attributes->has($argument->getName()) ? [$request->attributes->get($argument->getName())] : [];
28+
if ($argument->isVariadic()) {
29+
return [];
30+
}
31+
32+
$name = $argument->getName();
33+
if (!$request->attributes->has($name)) {
34+
return [];
35+
}
36+
37+
$value = $request->attributes->get($name);
38+
$type = $argument->getType();
39+
40+
// Skip when no type declaration or complex types; fall back to other resolvers/defaults
41+
if (null === $type || str_contains($type, '|') || str_contains($type, '&')) {
42+
return [$value];
43+
}
44+
45+
if ($value instanceof \Stringable) {
46+
$value = (string) $value;
47+
}
48+
49+
if ('string' === $type) {
50+
if (null === $value) {
51+
if ($argument->isNullable()) {
52+
return [null];
53+
}
54+
55+
throw new NotFoundHttpException(\sprintf('The value for the "%s" route parameter is invalid.', $name));
56+
}
57+
58+
if (\is_scalar($value)) {
59+
return [(string) $value];
60+
}
61+
62+
throw new NotFoundHttpException(\sprintf('The value for the "%s" route parameter is invalid.', $name));
63+
}
64+
65+
if ($filter = match ($type) {
66+
'int' => \FILTER_VALIDATE_INT,
67+
'float' => \FILTER_VALIDATE_FLOAT,
68+
'bool' => \FILTER_VALIDATE_BOOL,
69+
default => null,
70+
}) {
71+
$filtered = $request->attributes->filter($name, null, $filter, [
72+
'flags' => \FILTER_NULL_ON_FAILURE | \FILTER_REQUIRE_SCALAR,
73+
]);
74+
if (null === $filtered) {
75+
throw new NotFoundHttpException(\sprintf('The value for the "%s" route parameter is invalid.', $name));
76+
}
77+
78+
return [$filtered];
79+
}
80+
81+
return [$value];
2882
}
2983
}

src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/BackedEnumValueResolverTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public function testResolveThrowsOnUnexpectedType()
119119
$request = self::createRequest(['suit' => false]);
120120
$metadata = self::createArgumentMetadata('suit', Suit::class);
121121

122-
$this->expectException(\LogicException::class);
122+
$this->expectException(NotFoundHttpException::class);
123123
$this->expectExceptionMessage('Could not resolve the "Symfony\Component\HttpKernel\Tests\Fixtures\Suit $suit" controller argument: expecting an int or string, got "bool".');
124124

125125
$resolver->resolve($request, $metadata);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\Tests\Controller\ArgumentResolver;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver;
17+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
18+
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
19+
20+
class RequestAttributeValueResolverTest extends TestCase
21+
{
22+
public function testValidIntWithinRangeWorks()
23+
{
24+
$resolver = new RequestAttributeValueResolver();
25+
$request = new Request();
26+
$request->attributes->set('id', '123');
27+
$metadata = new ArgumentMetadata('id', 'int', false, false, null);
28+
29+
$result = iterator_to_array($resolver->resolve($request, $metadata));
30+
31+
$this->assertSame([123], $result);
32+
}
33+
34+
public function testInvalidStringBecomes404()
35+
{
36+
$resolver = new RequestAttributeValueResolver();
37+
$request = new Request();
38+
$request->attributes->set('id', 'abc');
39+
$metadata = new ArgumentMetadata('id', 'int', false, false, null);
40+
41+
$this->expectException(NotFoundHttpException::class);
42+
iterator_to_array($resolver->resolve($request, $metadata));
43+
}
44+
45+
public function testOutOfRangeIntBecomes404()
46+
{
47+
$resolver = new RequestAttributeValueResolver();
48+
$request = new Request();
49+
// one more than PHP_INT_MAX on 64-bit (string input)
50+
$request->attributes->set('id', '9223372036854775808');
51+
$metadata = new ArgumentMetadata('id', 'int', false, false, null);
52+
53+
$this->expectException(NotFoundHttpException::class);
54+
iterator_to_array($resolver->resolve($request, $metadata));
55+
}
56+
}

0 commit comments

Comments
 (0)