Некоторое время назад была статья про нововведения в PHP7. Я написал в комментариях, что раз уж добавлены разные новые операторы для упрощения кода в стандартных конструкциях, то неплохо было бы добавить еще и оператор для вывода HTML-экранированных данных. Получил в ответ несколько комментов про шаблонизаторы и задумался. Я знаю про шаблонизаторы, но есть много проектов, в которых они не используются, которые написаны на самописных движках, на CMS, либо на фреймворках, в которых нет шаблонизатора по умолчанию. Эти проекты продолжают развиваться и требуют писать код.
В этой статье я хочу изложить некоторые аргументы за то, что такой оператор будет полезен. И, возможно, получить обоснованные аргументы против.
Я предлагаю оператор <?~ $value ?>
как короткую запись для <?= htmlspecialchars($value, ENT_QUOTES) ?>
. Это позволит уменьшить копи-пасту вызовов и улучшить безопасность приложений без движков шаблонизации, так как в таких приложениях это частая операция. Обычно можно перевести приложение на новую версию языка программирования, но практически невозможно переписать все имеющиеся PHP-шаблоны на специальный шаблонизатор. Вариант сделать функцию <?= h($value) ?>
это не выход, так как копи-паста все равно остается, и можно когда-нибудь забыть ее вызвать.
Поискав на bugs.php.net, я нашел один активный feature request с 2012 года. Там в комментариях была предложена форма записи <?~ ?>
, которая показалась мне удобной. Написав там примерно то же самое, что и в статье про PHP7, получил ответ, что это потребует процесса RFC.
Я пообщался с разработчиками в рассылке PHP Internals list и получил ответ, что это уже обсуждалось много раз.
Ссылки:
marc.info/?t=145851323800001
marc.info/?t=135082660600002
marc.info/?t=144225546000001
wiki.php.net/rfc/escaper
wiki.php.net/rfc/taint
bugs.php.net/bug.php?id=62574
bugs.php.net/bug.php?id=20310
bugs.php.net/bug.php?id=16007
bugs.php.net/bug.php?id=3284
Основной аргумент в том, что экранирование контекстно-зависимо. У нас есть разные контексты — HTML/URL/JS/CSS — и делать какой-то оператор только для одного контекста неправильно, а для всех сразу сложно.
На самом деле это немного не так. Это не взаимоисключающие контексты, HTML может присутствовать или нет независимо от остальных. Кроме того, для JS и CSS контекста нельзя применить понятие «экранирование» (escaping), потому что это другие языки со своим синтаксисом. Правильная запись для них не сводится к добавлению слэшей и замене спец-символов.
Рассмотрим пример.
<a href="/things/<?= $thing['name'] ?>" onclick="alert('<?= $thing['name'] ?>');">
<?= $thing['name'] ?>
</a>
На первый взгляд может показаться, что тут везде нужно разное экранирование. Но это не так. Вызов htmlspecialchars() нужен во всех трех случаях.
<?php $thing = ['name' => 'Say "Hello")']; ?>
<a
href="/things/<?= htmlspecialchars(urlencode($thing['name'])) ?>"
onclick="alert(<?= htmlspecialchars(json_encode($thing['name']), ENT_QUOTES) ?>);"
>
<?= htmlspecialchars($thing['name']) ?>
</a>
Несмотря на то, что urlencode() возвращает безопасную для HTML строку, и можно не использовать htmlspecialchars(), сочетание htmlspecialchars() + urlencode() встречается в обработке различных фильтров.
Пример:
<?php
$postData = ['contains_text' => 'Say "Hello")'];
$filterUrl = '/my_route/?state=active';
if ($postData['contains_text']) $filterUrl .= '&' . 'contains_text=' . urlencode($postData['contains_text']);
$pageNumber = 1;
?>
<a
href="<?= htmlspecialchars($filterUrl) ?>"
onclick="alert(<?= htmlspecialchars(json_encode($postData['contains_text']), ENT_QUOTES) ?>);"
>
<?= $pageNumber ?>
</a>
При обработке веб-страниц htmlspecialchars() не нужен только внутри тегов style
и script
.
1 HTML Содержимое тега, любые атрибуты тега (включает следующие 3 варианта)
2 HTML + URL Атрибуты href, action, различные варианты типа data-url
3 HTML + JS Атрибуты on-event - onclick, onkeypress и т.д.
4 HTML + CSS Атрибут style
5 URL -
6 JS Тег <script></script>
7 CSS Тег <style></style>
8 non-HTML Экранирование может зависеть не только от формата, но и от задачи.
Пункт 5 — отдельно в HTML-документе не встречается, только в составе других контекстов.
Пункт 6 — встречается довольно часто, это единственный практический случай, когда нам не надо применять html-экранирование. Но такая связка PHP+JS считается не очень хорошим стилем, лучше использовать data-атрибуты, особенно в мультиязычных приложениях. А для них нужен htmlspecialchars().
Пункт 7 — очень редкий случай, обычно PHP там не используется.
Пункт 8 — для этого случая безопасное экранирование принципиально невозможно придумать заранее.
Таким образом, для наиболее часто встречающихся на практике случаев экранировать HTML не надо только в одном случае, в остальных четырех экранирование необходимо. Следовательно, специальный оператор будет полезен, и многочисленные дискуссии тому подтверждение.
Оператор <?~ ?>
не требует настроек в «php.ini» и не влияет на остальные теги и операторы. Его удобно набирать, все символы набираются с Shift, и меньше вероятность написать <?= ?>
вместо него, так как тильда находится с другой стороны клавиатуры. Если вы по каким-то причинам не используете htmlspecialchars()
, то для вас ничего не поменяется. Он не является универсальным оператором для экранирования любых данных, это просто короткая запись для вызова <?php echo htmlspecialchars($str, ENT_QUOTES) ?>
, так же как <?= ?>
для <?php echo $str; ?>
Внимание. Если вы пишете приложения, к примеру, только на Twig и Symfony, или последние N лет делаете только API, то большая просьба не голосовать, а то получится нерепрезентативная выборка. Для вас сделан третий опрос. В первых двух интересует мнение людей, которые реально с этим работают.
Аргументированные комментарии за и против приветствуются.
Автор: michael_vostrikov