From 2cad921b3f9256169a4ded84c9c9fa04b9c593d2 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 30 Jun 2025 21:36:53 +0100 Subject: [PATCH] fix: prevent callable strings --- composer.json | 5 ++ phpunit.xml | 23 ++++---- src/Http/Http.php | 2 +- tests/HttpTest.php | 59 ++++++++++++++++++- ...questTest.php => UtopiaFPMRequestTest.php} | 0 5 files changed, 76 insertions(+), 13 deletions(-) rename tests/{UtopiaRequestTest.php => UtopiaFPMRequestTest.php} (100%) diff --git a/composer.json b/composer.json index b5c168bc..06ff7c94 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,11 @@ "Tests\\E2E\\": "tests/e2e" } }, + "autoload-dev": { + "psr-4": { + "Utopia\\Http\\Tests\\": "tests/" + } + }, "scripts": { "lint": "vendor/bin/pint --test", "format": "vendor/bin/pint", diff --git a/phpunit.xml b/phpunit.xml index de6deb0b..e33b301e 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,17 +1,18 @@ - + ./tests/e2e/Client.php ./tests/ - \ No newline at end of file + diff --git a/src/Http/Http.php b/src/Http/Http.php index 37638edb..f0ab42d8 100755 --- a/src/Http/Http.php +++ b/src/Http/Http.php @@ -740,7 +740,7 @@ protected function getArguments(Hook $hook, string $context, array $values, arra $paramExists = $existsInRequest || $existsInValues; $arg = $existsInRequest ? $requestParams[$key] : $param['default']; - if (\is_callable($arg)) { + if (\is_callable($arg) && !\is_string($arg)) { $arg = \call_user_func_array($arg, $this->getResources($param['injections'])); } $value = $existsInValues ? $values[$key] : $arg; diff --git a/tests/HttpTest.php b/tests/HttpTest.php index 0d314cd3..ae6c5dd6 100755 --- a/tests/HttpTest.php +++ b/tests/HttpTest.php @@ -454,7 +454,7 @@ public function providerRouteMatching(): array /** * @dataProvider providerRouteMatching */ - public function testCanMatchRoute(string $method, string $path, string $url = null): void + public function testCanMatchRoute(string $method, string $path, ?string $url = null): void { $url ??= $path; $expected = null; @@ -616,4 +616,61 @@ public function testWildcardRoute(): void $_SERVER['REQUEST_METHOD'] = $method; $_SERVER['REQUEST_URI'] = $uri; } + + public function testCallableStringParametersNotExecuted(): void + { + // Test that callable strings (like function names) are not executed + $route = new Route('GET', '/test-callable-string'); + + $route + ->param('callback', 'phpinfo', new Text(200), 'callback param', true) + ->action(function ($callback) { + // If the string 'phpinfo' was executed as a function, + // it would output PHP info. Instead, it should just be the string. + echo 'callback-value: ' . $callback; + }); + + \ob_start(); + $this->http->execute($route, new Request(), '1'); + $result = \ob_get_contents(); + \ob_end_clean(); + + $this->assertEquals('callback-value: phpinfo', $result); + + // Test with request parameter that is a callable string + $route2 = new Route('GET', '/test-callable-string-param'); + + $route2 + ->param('func', 'default', new Text(200), 'func param', false) + ->action(function ($func) { + echo 'func-value: ' . $func; + }); + + \ob_start(); + $request = new UtopiaFPMRequestTest(); + $request::_setParams(['func' => 'system']); + $this->http->execute($route2, $request, '1'); + $result = \ob_get_contents(); + \ob_end_clean(); + + $this->assertEquals('func-value: system', $result); + + // Test callable closure still works + $route3 = new Route('GET', '/test-callable-closure'); + + $route3 + ->param('generated', function () { + return 'generated-value'; + }, new Text(200), 'generated param', true) + ->action(function ($generated) { + echo 'generated: ' . $generated; + }); + + \ob_start(); + $this->http->execute($route3, new Request(), '1'); + $result = \ob_get_contents(); + \ob_end_clean(); + + $this->assertEquals('generated: generated-value', $result); + } } diff --git a/tests/UtopiaRequestTest.php b/tests/UtopiaFPMRequestTest.php similarity index 100% rename from tests/UtopiaRequestTest.php rename to tests/UtopiaFPMRequestTest.php