PHP и атрибут target="_blank": решения для веб-разработчика
Работа с атрибутом target="_blank" в PHP
При генерации HTML-ссылок на PHP часто требуется открыть страницу в новой вкладке. Без дополнительных мер это ведёт к уязвимости tabnabbing (злонамеренный скрипт может изменить содержимое исходной страницы). Основное решение - при использовании target="_blank" всегда добавлять rel="noopener noreferrer".
Основное решение: универсальная функция для безопасной ссылки
Функция принимает URL, текст ссылки и опциональные атрибуты. Автоматически добавляет target="_blank" и rel="noopener noreferrer".
<?php
function safe_external_link($url, $text, $additional_attrs = []) {
$escaped_url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
$escaped_text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
$attrs = 'target="_blank" rel="noopener noreferrer"';
foreach ($additional_attrs as $key => $value) {
$attrs .= ' ' . htmlspecialchars($key, ENT_QUOTES, 'UTF-8') . '="' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '"';
}
return '<a href="' . $escaped_url . '" ' . $attrs . '>' . $escaped_text . '</a>';
}
echo safe_external_link('https://example.com', 'Пример ссылки', ['class' => 'ext-link']);
?>
<a href="https://example.com" target="_blank" rel="noopener noreferrer" class="ext-link">Пример ссылки</a>
Возможная проблема:
Если не экранировать текст ссылки, возможна XSS-атака. Обязательно используйте htmlspecialchars(). В функции выше это учтено.
Как просто добавить target="_blank" в ссылку без дополнительной защиты?
Вариант 1: Прямое указание атрибута в строке
<?php
$url = 'https://example.com';
$text = 'Перейти';
echo '<a href="' . htmlspecialchars($url) . '" target="_blank">' . htmlspecialchars($text) . '</a>';
?>
Типичная ошибка:
Пропущен rel, что делает исходную страницу уязвимой. Не рекомендуется к применению без осознания риска.
Как защитить ссылку от tabnabbing с помощью rel?
Вариант 2: Добавление rel вручную
<?php
$url = 'https://example.com';
echo '<a href="' . htmlspecialchars($url) . '" target="_blank" rel="noopener noreferrer">Открыть в новой вкладке</a>';
?>
Этот способ прост и безопасен, но требует повторения кода.
Как реализовать target="_blank" в шаблонизаторе Twig?
Вариант 3: Использование Twig с автоматической вставкой rel
{# В шаблоне Twig #}
<a href="{{ url }}" target="_blank" rel="noopener noreferrer">{{ text }}</a>
Особенность:
Twig по умолчанию экранирует переменные, но не добавляет rel. Атрибут нужно прописывать явно.
Как добавить target="_blank" только для внешних ссылок?
Вариант 4: Определение внешних ссылок по домену
<?php
function is_external($url, $base_host) {
$host = parse_url($url, PHP_URL_HOST);
return $host !== null && $host !== $base_host;
}
$base_host = $_SERVER['HTTP_HOST'];
$links = [
'https://example.com',
'/internal/page',
'https://mysite.com/relative'
];
foreach ($links as $link) {
$target = is_external($link, $base_host) ? 'target="_blank" rel="noopener noreferrer"' : '';
echo '<a href="' . htmlspecialchars($link) . '" ' . $target . '>ссылка</a><br>';
}
?>
Функция parse_url извлекает хост. Если хост отличается от текущего, ссылка считается внешней.
Проблема:
Не все URL содержат хост (относительные). Нужно проверить, что $host не равен null. В примере это учтено.
Расширенные примеры работы с target="_blank" в PHP
Пример 1: Обёртка для ссылок с использованием DOMDocument
Парсинг HTML-строки и добавление атрибутов ко всем ссылкам, ведущим на внешние ресурсы.
<?php
function add_target_blank_to_external($html, $base_url) {
$dom = new DOMDocument();
@$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$links = $dom->getElementsByTagName('a');
$base_host = parse_url($base_url, PHP_URL_HOST);
foreach ($links as $link) {
$href = $link->getAttribute('href');
if ($href === '') continue;
$host = parse_url($href, PHP_URL_HOST);
if ($host !== null && $host !== $base_host) {
$link->setAttribute('target', '_blank');
$link->setAttribute('rel', 'noopener noreferrer');
}
}
return $dom->saveHTML();
}
$html = '<a href="https://evil.com">Плохая ссылка</a> <a href="/local">Локальная</a>';
echo add_target_blank_to_external($html, 'http://mysite.com');
?>
<a href="https://evil.com" target="_blank" rel="noopener noreferrer">Плохая ссылка</a> <a href="/local">Локальная</a>
Пример 2: Регулярное выражение для замены target в строке HTML
Быстрая замена, но менее надёжная (не учитывает вложенные кавычки).
<?php
$html = '<a href="http://example.com" target="_blank">link</a>';
// Добавляем rel, если его нет
$pattern = '/<a\s+([^>]*?)target="_blank"(?!.*?rel=)/i';
$replacement = '<a $1 target="_blank" rel="noopener noreferrer"';
$result = preg_replace($pattern, $replacement, $html);
echo $result;
?>
<a href="http://example.com" target="_blank" rel="noopener noreferrer">link</a>
Ограничение:
Регулярные выражения не подходят для сложной вложенности HTML (например, кавычки внутри атрибута). Лучше использовать DOM.
Пример 3: C использованием фильтрации URL через filter_var
Проверка, является ли URL абсолютным (схема присутствует).
<?php
function is_absolute_url($url) {
return filter_var($url, FILTER_VALIDATE_URL) !== false;
}
$links = ['https://example.com', '/relative'];
foreach ($links as $url) {
if (is_absolute_url($url) && strpos($url, 'http') === 0) {
echo '<a href="' . htmlspecialchars($url) . '" target="_blank" rel="noopener noreferrer">Ссылка</a><br>';
} else {
echo '<a href="' . htmlspecialchars($url) . '">Ссылка</a><br>';
}
}
?>
<a href="https://example.com" target="_blank" rel="noopener noreferrer">Ссылка</a><br> <a href="/relative">Ссылка</a><br>
Пример 4: Хелпер для фреймворков (Laravel-подобный стиль)
<?php
class LinkBuilder {
public static function external($url, $text) {
return sprintf(
'<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>',
htmlspecialchars($url, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($text, ENT_QUOTES, 'UTF-8')
);
}
}
echo LinkBuilder::external('https://php.net', 'PHP');
?>
<a href="https://php.net" target="_blank" rel="noopener noreferrer">PHP</a>