escapeButPreserveUris() matched the scheme with "[A-z]+?://", which accepts any scheme. A frame comment containing javascript://x.co/?%0Aalert(1) survives htmlspecialchars() untouched and was then wrapped in an anchor, so clicking it executed script in the origin of the error page. "[A-z]" is also a code point range rather than a letter class, so it additionally matched the six characters between "Z" and "a", making "_://x.co/a" a link too. Match the scheme with "https?" instead, case-insensitively so uppercase "HTTP://" keeps working. Other schemes stay as escaped plain text. The editor integration links are not affected, they are emitted directly by the template and never pass through this method. Fixes #791
184 lines
5.5 KiB
PHP
184 lines
5.5 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 <a href=''>Foo</a> 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 <a href=''><a href=\"http://google.com\" target=\"_blank\" rel=\"noreferrer noopener\">http://google.com</a></a> test string"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @covers Whoops\Util\TemplateHelper::escapeButPreserveUris
|
|
*/
|
|
public function testEscapeButPreserveUrisIgnoresNonHttpSchemes()
|
|
{
|
|
// None of these contain HTML special characters, so escaping leaves them
|
|
// untouched and the only question is whether they get turned into links.
|
|
$originals = [
|
|
// Clicking such a link would run alert(1): the browser decodes %0A to a
|
|
// newline, which ends the "//x.co/?" JavaScript comment.
|
|
'javascript://x.co/?%0Aalert(1)',
|
|
'JAVASCRIPT://x.co/?%0Aalert(1)',
|
|
'vbscript://x.co/?%0Amsgbox(1)',
|
|
'data://text.html/?%0Ax',
|
|
// "_" is between "Z" and "a" in ASCII, so it used to pass as a scheme.
|
|
'_://x.co/a',
|
|
'See javascript://x.co/?%0Aalert(document.cookie) for details',
|
|
];
|
|
|
|
foreach ($originals as $original) {
|
|
$this->assertSame($original, $this->helper->escapeButPreserveUris($original));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @covers Whoops\Util\TemplateHelper::escapeButPreserveUris
|
|
*/
|
|
public function testEscapeButPreserveUrisStillLinksHttpUris()
|
|
{
|
|
$originals = [
|
|
'http://example.com/a',
|
|
'https://example.com/a',
|
|
'HTTPS://example.com/a',
|
|
];
|
|
|
|
foreach ($originals as $original) {
|
|
$this->assertSame(
|
|
"<a href=\"$original\" target=\"_blank\" rel=\"noreferrer noopener\">$original</a>",
|
|
$this->helper->escapeButPreserveUris($original)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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('…/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<o>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",
|
|
]);
|
|
}
|
|
}
|