diff --git a/lib/docset_renderer.php b/lib/docset_renderer.php index 032cd21..4a2e9af 100644 --- a/lib/docset_renderer.php +++ b/lib/docset_renderer.php @@ -93,7 +93,12 @@ class DocsetRenderer if($value === '' || str_starts_with($value, '#')) return $value; $lower = strtolower($value); - foreach(array('http://', 'https://', 'mailto:', 'data:', 'javascript:', 'about:') as $prefix) + foreach(array('javascript:', 'vbscript:') as $prefix) + { + if(str_starts_with($lower, $prefix)) + return '#blocked-unsafe-url'; + } + foreach(array('http://', 'https://', 'mailto:', 'data:', 'about:') as $prefix) { if(str_starts_with($lower, $prefix)) return $value; diff --git a/tests/smoke.php b/tests/smoke.php index 4ce5137..3d7b277 100644 --- a/tests/smoke.php +++ b/tests/smoke.php @@ -9,6 +9,7 @@ require_once 'lib/ulib.php'; require_once 'lib/components.php'; require_once 'lib/theme_helpers.php'; require_once 'lib/docset_repository.php'; +require_once 'lib/docset_renderer.php'; function assert_true($condition, string $message): void { @@ -30,6 +31,11 @@ $repo = new DocsetRepository(); $docsets = $repo->listDocsets(); assert_true(is_array($docsets), 'docset list returns an array'); +$sanitized = DocsetRenderer::renderInlineHtml('
bad link
', $repo, 'PHP', '.'); +assert_true(!str_contains(strtolower($sanitized), 'javascript:'), 'inline renderer blocks javascript URLs from docset HTML'); +assert_true(!str_contains(strtolower($sanitized), 'onclick='), 'inline renderer strips event handler attributes from docset HTML'); +assert_true(str_contains($sanitized, '#blocked-unsafe-url'), 'inline renderer keeps unsafe links inert and visible'); + $phpDocset = $repo->getDocset('PHP'); if($phpDocset) { @@ -39,7 +45,6 @@ if($phpDocset) assert_true(isset($results[0]['href']), 'search result contains view URL'); $doc = $repo->resolveDocumentPath('PHP', 'www.php.net/manual/en/function.array-map.html'); assert_true($doc !== null, 'PHP docset document resolves'); - require_once 'lib/docset_renderer.php'; $html = DocsetRenderer::renderInlineHtml((string)file_get_contents($doc['file']), $repo, 'PHP', dirname($doc['relative'])); assert_true(str_contains($html, 'array_map'), 'inline renderer includes document content'); $rewrittenLink = str_replace('&', '&', URL::Link('view', array('docset' => 'PHP', 'path' => 'www.php.net/manual/en/language.types.array.html')));