diff --git a/src/Profiler/Dumper/HtmlDumper.php b/src/Profiler/Dumper/HtmlDumper.php index cdab2de59..a5e59e498 100644 --- a/src/Profiler/Dumper/HtmlDumper.php +++ b/src/Profiler/Dumper/HtmlDumper.php @@ -32,16 +32,21 @@ final class HtmlDumper extends BaseDumper protected function formatTemplate(Profile $profile, $prefix): string { - return \sprintf('%s└ %s', $prefix, self::$colors['template'], $profile->getTemplate()); + return \sprintf('%s└ %s', $prefix, self::$colors['template'], self::escape($profile->getTemplate())); } protected function formatNonTemplate(Profile $profile, $prefix): string { - return \sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), self::$colors[$profile->getType()] ?? 'auto', $profile->getName()); + return \sprintf('%s└ %s::%s(%s)', $prefix, self::escape($profile->getTemplate()), $profile->getType(), self::$colors[$profile->getType()] ?? 'auto', self::escape($profile->getName())); } protected function formatTime(Profile $profile, $percent): string { return \sprintf('%.2fms/%.0f%%', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent); } + + private static function escape(string $value): string + { + return htmlspecialchars($value, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'); + } } diff --git a/tests/Profiler/Dumper/HtmlTest.php b/tests/Profiler/Dumper/HtmlTest.php index a89fcfc46..b1170a83a 100644 --- a/tests/Profiler/Dumper/HtmlTest.php +++ b/tests/Profiler/Dumper/HtmlTest.php @@ -21,6 +21,7 @@ namespace Twig\Tests\Profiler\Dumper; */ use Twig\Profiler\Dumper\HtmlDumper; +use Twig\Profiler\Profile; class HtmlTest extends ProfilerTestCase { @@ -39,4 +40,23 @@ class HtmlTest extends ProfilerTestCase EOF, $dumper->dump($this->getProfile())); } + + public function testDumpEscapesTemplateAndProfileNames() + { + $root = new Profile('main'); + $child = new Profile('', Profile::TEMPLATE); + $grandchild = new Profile('', Profile::MACRO, ''); + + (new \ReflectionProperty($child, 'profiles'))->setValue($child, [$grandchild]); + (new \ReflectionProperty($root, 'profiles'))->setValue($root, [$child]); + + $output = (new HtmlDumper())->dump($root); + + $this->assertStringNotContainsString('', $output); + $this->assertStringNotContainsString('', $output); + $this->assertStringNotContainsString('', $output); + $this->assertStringContainsString('<img src=x onerror=alert(1)>', $output); + $this->assertStringContainsString('<img src=x onerror=alert(2)>', $output); + $this->assertStringContainsString('<img src=x onerror=alert(3)>', $output); + } }