diff --git a/src/Renderers/SpanNodeRenderer.php b/src/Renderers/SpanNodeRenderer.php index 0fc9f9c..2510e82 100644 --- a/src/Renderers/SpanNodeRenderer.php +++ b/src/Renderers/SpanNodeRenderer.php @@ -52,6 +52,11 @@ public function link(?string $url, string $title, array $attributes = []): strin $this->urlChecker->checkUrl($url); } + if (!$this->isSafeUrl($url)) { + $attributes['rel'] = 'external noopener noreferrer'; + $attributes['target'] = '_blank'; + } + return $this->templateRenderer->render( 'link.html.twig', [ @@ -80,4 +85,20 @@ private function isExternalUrl($url): bool { return u($url)->containsAny('://'); } + + /* + * If the URL is considered safe, it's opened in the same browser tab; + * otherwise it's opened in a new tab and with some strict security options. + */ + private function isSafeUrl(string $url): bool + { + // The following are considered Symfony URLs: + // * https://symfony.com/[...] + // * https://[...].symfony.com/ (e.g. insight.symfony.com, etc.) + // * https://symfony.wip/[...] (used for internal/local development) + $isSymfonyUrl = preg_match('{^http(s)?://(.*\.)?symfony.(com|wip)}', $url); + $isRelativeUrl = !str_starts_with($url, 'http://') && !str_starts_with($url, 'https://'); + + return $isSymfonyUrl || $isRelativeUrl; + } } diff --git a/tests/fixtures/expected/blocks/references/php-class.html b/tests/fixtures/expected/blocks/references/php-class.html index de4cdbc..dc8c36f 100644 --- a/tests/fixtures/expected/blocks/references/php-class.html +++ b/tests/fixtures/expected/blocks/references/php-class.html @@ -3,11 +3,11 @@ - + -

ArrayAccess

+

ArrayAccess

- \ No newline at end of file + diff --git a/tests/fixtures/expected/blocks/references/php-function.html b/tests/fixtures/expected/blocks/references/php-function.html index 0172ade..0a17f0a 100644 --- a/tests/fixtures/expected/blocks/references/php-function.html +++ b/tests/fixtures/expected/blocks/references/php-function.html @@ -3,11 +3,11 @@ - + -

trigger_error

+

trigger_error

- \ No newline at end of file + diff --git a/tests/fixtures/expected/blocks/references/php-method.html b/tests/fixtures/expected/blocks/references/php-method.html index dbcf43f..073efb9 100644 --- a/tests/fixtures/expected/blocks/references/php-method.html +++ b/tests/fixtures/expected/blocks/references/php-method.html @@ -3,11 +3,11 @@ - + -

Locale::getDefault()

+

Locale::getDefault()

- \ No newline at end of file + diff --git a/tests/fixtures/expected/main/datetime.html b/tests/fixtures/expected/main/datetime.html index 41a6b1e..49d76d7 100644 --- a/tests/fixtures/expected/main/datetime.html +++ b/tests/fixtures/expected/main/datetime.html @@ -3,7 +3,7 @@ - + @@ -12,7 +12,7 @@

This field type allows the user to modify data that represents a specific date and time (e.g. 1984-06-05 12:15:30).

- + @@ -67,8 +67,8 @@

doRequest(). Or a namespace: Constraints. -Or a PHP function: parse_ini_file. -Or a PHP method! Locale::getDefault().

+Or a PHP function: parse_ini_file. +Or a PHP method! Locale::getDefault().

Underlying Data Type