diff --git a/src/Whoops/Util/TemplateHelper.php b/src/Whoops/Util/TemplateHelper.php
index 5612c0b..c5913eb 100644
--- a/src/Whoops/Util/TemplateHelper.php
+++ b/src/Whoops/Util/TemplateHelper.php
@@ -80,6 +80,10 @@ class TemplateHelper
* Escapes a string for output in an HTML document, but preserves
* URIs within it, and converts them to clickable anchor elements.
*
+ * Only http and https URIs are turned into links. Any other scheme is left as
+ * plain text, because schemes such as javascript: and data: would otherwise
+ * become clickable script execution on the error page.
+ *
* @param string $raw
* @return string
*/
@@ -87,7 +91,7 @@ class TemplateHelper
{
$escaped = $this->escape($raw);
return preg_replace(
- "@([A-z]+?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@",
+ "@(https?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@i",
"$1",
$escaped
);
diff --git a/tests/Whoops/Util/TemplateHelperTest.php b/tests/Whoops/Util/TemplateHelperTest.php
index 086645a..75baf7a 100644
--- a/tests/Whoops/Util/TemplateHelperTest.php
+++ b/tests/Whoops/Util/TemplateHelperTest.php
@@ -63,6 +63,49 @@ class TemplateHelperTest extends TestCase
);
}
+ /**
+ * @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(
+ "$original",
+ $this->helper->escapeButPreserveUris($original)
+ );
+ }
+ }
+
/**
* @covers Whoops\Util\TemplateHelper::breakOnDelimiter
*/