Files
filp-whoops/tests/Whoops/Util/TemplateHelperTest.php
T
dualfroz 7f2cd8f154 fix(template-helper): restrict escapeButPreserveUris to http/https schemes
escapeButPreserveUris() matched any scheme via [A-z]+?://, which also
turns javascript://, vbscript:// and data:// URIs into clickable links
on the generated error page, allowing script execution on click (XSS).
The [A-z] class was also invalid, spanning extra ASCII punctuation.

Restrict the scheme to http/https, the only two schemes that make
sense to linkify on an error page, leaving other schemes as plain
escaped text.
2026-09-04 03:00:40 +02:00

179 lines
5.2 KiB
PHP

<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Util;
use Whoops\TestCase;
class TemplateHelperTest extends TestCase
{
/**
* @var TemplateHelper
*/
private $helper;
/**
* @before
*/
public function getReady()
{
$this->helper = new TemplateHelper();
}
/**
* @covers Whoops\Util\TemplateHelper::escapeButPreserveUris
* @covers Whoops\Util\TemplateHelper::escape
*/
public function testEscape()
{
$original = "This is a <a href=''>Foo</a> test string";
$this->assertEquals(
$this->helper->escape($original),
"This is a &lt;a href=&#039;&#039;&gt;Foo&lt;/a&gt; test string"
);
}
public function testEscapeBrokenUtf8()
{
// The following includes an illegal utf-8 sequence to test.
// Encoded in base64 to survive possible encoding changes of this file.
$original = base64_decode('VGhpcyBpcyBhbiBpbGxlZ2FsIHV0Zi04IHNlcXVlbmNlOiDD');
// Test that the escaped string is kinda similar in length, not empty
$this->assertLessThan(
10,
abs(strlen($original) - strlen($this->helper->escape($original)))
);
}
/**
* @covers Whoops\Util\TemplateHelper::escapeButPreserveUris
*/
public function testEscapeButPreserveUris()
{
$original = "This is a <a href=''>http://google.com</a> test string";
$this->assertEquals(
$this->helper->escapeButPreserveUris($original),
"This is a &lt;a href=&#039;&#039;&gt;<a href=\"http://google.com\" target=\"_blank\" rel=\"noreferrer noopener\">http://google.com</a>&lt;/a&gt; test string"
);
}
/**
* @covers Whoops\Util\TemplateHelper::escapeButPreserveUris
*/
public function testEscapeButPreserveUrisRejectsDangerousSchemes()
{
$dangerous = [
'javascript://x.co/?%0Aalert(1)',
'vbscript://x.co/?%0Amsgbox(1)',
'data://text/html,<script>alert(1)</script>',
];
foreach ($dangerous as $payload) {
$output = $this->helper->escapeButPreserveUris($payload);
$this->assertStringNotContainsString(
'<a href=',
$output,
"Payload '$payload' must not be turned into a clickable link"
);
}
}
/**
* @covers Whoops\Util\TemplateHelper::escapeButPreserveUris
*/
public function testEscapeButPreserveUrisAllowsHttpAndHttpsSchemes()
{
$this->assertEquals(
"<a href=\"http://google.com\" target=\"_blank\" rel=\"noreferrer noopener\">http://google.com</a>",
$this->helper->escapeButPreserveUris('http://google.com')
);
$this->assertEquals(
"<a href=\"https://google.com\" target=\"_blank\" rel=\"noreferrer noopener\">https://google.com</a>",
$this->helper->escapeButPreserveUris('https://google.com')
);
}
/**
* @covers Whoops\Util\TemplateHelper::breakOnDelimiter
*/
public function testBreakOnDelimiter()
{
$this->assertSame(
'<span class="delimiter">abc</span>-<span class="delimiter">123</span>-<span class="delimiter">456</span>',
$this->helper->breakOnDelimiter('-', 'abc-123-456')
);
}
/**
* @covers Whoops\Util\TemplateHelper::shorten
*/
public function testShorten()
{
$path = '/foo/bar/baz/abc.def';
$this->assertSame($path, $this->helper->shorten($path));
$this->helper->setApplicationRootPath('/foo/bar');
$this->assertSame('&hellip;/baz/abc.def', $this->helper->shorten($path));
}
/**
* @covers Whoops\Util\TemplateHelper::slug
*/
public function testSlug()
{
$this->assertEquals("hello-world", $this->helper->slug("Hello, world!"));
$this->assertEquals("potato-class", $this->helper->slug("Potato class"));
}
/**
* @covers Whoops\Util\TemplateHelper::render
*/
public function testRender()
{
$template = __DIR__ . "/../../fixtures/template.php";
ob_start();
$this->helper->render($template, ["name" => "B<o>b"]);
$output = ob_get_clean();
$this->assertEquals(
$output,
"hello-world\nMy name is B&lt;o&gt;b"
);
}
/**
* @covers Whoops\Util\TemplateHelper::setVariables
* @covers Whoops\Util\TemplateHelper::getVariables
* @covers Whoops\Util\TemplateHelper::setVariable
* @covers Whoops\Util\TemplateHelper::getVariable
* @covers Whoops\Util\TemplateHelper::delVariable
*/
public function testTemplateVariables()
{
$this->helper->setVariables([
"name" => "Whoops",
"type" => "library",
"desc" => "php errors for cool kids",
]);
$this->helper->setVariable("name", "Whoops!");
$this->assertEquals($this->helper->getVariable("name"), "Whoops!");
$this->helper->delVariable("type");
$this->assertEquals($this->helper->getVariables(), [
"name" => "Whoops!",
"desc" => "php errors for cool kids",
]);
}
}