From 913930d193acbf68fdc1894d2a3bce7c898a4755 Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Fri, 24 Nov 2023 10:49:19 +0100 Subject: [PATCH 1/2] [FEATURE] UrlencodeViewHelper for StandaloneFluid UrlencodeViewHelper doesn't have any dependencies to TYPO3, so it can be moved to Fluid Standalone. --- .../Format/UrlencodeViewHelper.php | 83 +++++++++++++++++++ .../Format/UrlencodeViewHelperTest.php | 76 +++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 src/ViewHelpers/Format/UrlencodeViewHelper.php create mode 100644 tests/Functional/ViewHelpers/Format/UrlencodeViewHelperTest.php diff --git a/src/ViewHelpers/Format/UrlencodeViewHelper.php b/src/ViewHelpers/Format/UrlencodeViewHelper.php new file mode 100644 index 000000000..15954bc67 --- /dev/null +++ b/src/ViewHelpers/Format/UrlencodeViewHelper.php @@ -0,0 +1,83 @@ +foo @+%/ + * + * ``foo%20%40%2B%25%2F`` :php:`rawurlencode()` applied. + * + * Inline notation + * --------------- + * + * :: + * + * {text -> f:format.urlencode()} + * + * Url encoded text :php:`rawurlencode()` applied. + */ +final class UrlencodeViewHelper extends AbstractViewHelper +{ + use CompileWithContentArgumentAndRenderStatic; + + /** + * Output is escaped already. We must not escape children, to avoid double encoding. + * + * @var bool + */ + protected $escapeChildren = false; + + public function initializeArguments(): void + { + $this->registerArgument('value', 'string', 'string to format'); + } + + /** + * Escapes special characters with their escaped counterparts as needed using PHPs rawurlencode() function. + * + * @see https://www.php.net/manual/function.rawurlencode.php + * @return mixed + */ + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) + { + $value = $renderChildrenClosure(); + if (!is_string($value) && !(is_object($value) && method_exists($value, '__toString'))) { + return $value; + } + return rawurlencode((string)$value); + } + + /** + * Explicitly set argument name to be used as content. + */ + public function resolveContentArgumentName(): string + { + return 'value'; + } +} diff --git a/tests/Functional/ViewHelpers/Format/UrlencodeViewHelperTest.php b/tests/Functional/ViewHelpers/Format/UrlencodeViewHelperTest.php new file mode 100644 index 000000000..23c42e0b5 --- /dev/null +++ b/tests/Functional/ViewHelpers/Format/UrlencodeViewHelperTest.php @@ -0,0 +1,76 @@ + [ + '', + 'Source', + ], + 'renderUsesChildnodesAsSourceIfSpecified' => [ + 'Source', + 'Source', + ], + 'renderDoesNotModifyValueIfItDoesNotContainSpecialCharacters' => [ + 'StringWithoutSpecialCharacters', + 'StringWithoutSpecialCharacters', + ], + 'renderEncodesString' => [ + 'Foo @+%/ "', + 'Foo%20%40%2B%25%2F%20%22', + ], + ]; + } + + /** + * @test + * @dataProvider renderDataProvider + */ + public function render(string $template, string $expected): void + { + $view = new TemplateView(); + $view->getRenderingContext()->setCache(self::$cache); + $view->getRenderingContext()->getTemplatePaths()->setTemplateSource($template); + self::assertSame($expected, $view->render()); + + $view = new TemplateView(); + $view->getRenderingContext()->setCache(self::$cache); + $view->getRenderingContext()->getTemplatePaths()->setTemplateSource($template); + self::assertSame($expected, $view->render()); + } + + /** + * Ensures that objects are handled properly: + * + class having __toString() method gets tags stripped off + * + * @test + */ + public function renderEscapesObjectIfPossible(): void + { + $toStringClass = new class () { + public function __toString(): string + { + return ''; + } + }; + $view = new TemplateView(); + $view->getRenderingContext()->setCache(self::$cache); + $view->getRenderingContext()->getTemplatePaths()->setTemplateSource('{value}'); + $view->assign('value', $toStringClass); + self::assertEquals('%3Cscript%3Ealert%28%27%22xss%22%27%29%3C%2Fscript%3E', $view->render()); + } +} From e99c8ba9be58ca9e076dfe00e0571acbdd576a76 Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Fri, 24 Nov 2023 11:29:00 +0100 Subject: [PATCH 2/2] [BUGFIX] Throw exceptions for invalid input --- .../Format/UrlencodeViewHelper.php | 12 ++++--- .../Format/UrlencodeViewHelperTest.php | 31 +++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/ViewHelpers/Format/UrlencodeViewHelper.php b/src/ViewHelpers/Format/UrlencodeViewHelper.php index 15954bc67..cc1bc0c72 100644 --- a/src/ViewHelpers/Format/UrlencodeViewHelper.php +++ b/src/ViewHelpers/Format/UrlencodeViewHelper.php @@ -9,6 +9,7 @@ namespace TYPO3Fluid\Fluid\ViewHelpers\Format; +use Stringable; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic; @@ -62,13 +63,16 @@ public function initializeArguments(): void * Escapes special characters with their escaped counterparts as needed using PHPs rawurlencode() function. * * @see https://www.php.net/manual/function.rawurlencode.php - * @return mixed + * @return string */ - public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string { $value = $renderChildrenClosure(); - if (!is_string($value) && !(is_object($value) && method_exists($value, '__toString'))) { - return $value; + if (is_array($value)) { + throw new \InvalidArgumentException('Specified array cannot be converted to string.', 1700821579); + } + if (is_object($value) && !($value instanceof Stringable)) { + throw new \InvalidArgumentException('Specified object cannot be converted to string.', 1700821578); } return rawurlencode((string)$value); } diff --git a/tests/Functional/ViewHelpers/Format/UrlencodeViewHelperTest.php b/tests/Functional/ViewHelpers/Format/UrlencodeViewHelperTest.php index 23c42e0b5..8e7947c82 100644 --- a/tests/Functional/ViewHelpers/Format/UrlencodeViewHelperTest.php +++ b/tests/Functional/ViewHelpers/Format/UrlencodeViewHelperTest.php @@ -9,6 +9,7 @@ namespace TYPO3Fluid\Fluid\Tests\Functional\ViewHelpers\Format; +use stdClass; use TYPO3Fluid\Fluid\Tests\Functional\AbstractFunctionalTestCase; use TYPO3Fluid\Fluid\View\TemplateView; @@ -73,4 +74,34 @@ public function __toString(): string $view->assign('value', $toStringClass); self::assertEquals('%3Cscript%3Ealert%28%27%22xss%22%27%29%3C%2Fscript%3E', $view->render()); } + + public static function throwsExceptionForInvalidInputDataProvider(): array + { + return [ + 'array input' => [ + [1, 2, 3], + 1700821579, + 'Specified array cannot be converted to string.', + ], + 'object input' => [ + new stdClass(), + 1700821578, + 'Specified object cannot be converted to string.', + ], + ]; + } + + /** + * @test + * @dataProvider throwsExceptionForInvalidInputDataProvider + */ + public function throwsExceptionForInvalidInput(mixed $value, int $expectedExceptionCode, string $expectedExceptionMessage): void + { + self::expectExceptionCode($expectedExceptionCode); + self::expectExceptionMessage($expectedExceptionMessage); + $view = new TemplateView(); + $view->getRenderingContext()->getTemplatePaths()->setTemplateSource('{value}'); + $view->assign('value', $value); + $view->render(); + } }