Pull to refresh

Опрос. Новый тег <?~ $value ?> для HTML-экранирования данных в PHP

Reading time 4 min
Views 13K
Некоторое время назад была статья про нововведения в 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
marc.info/?t=101129596100006
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; ?>

UPD:
Описание реализации

Ссылки:
RFC: https://wiki.php.net/rfc/escaping_operator
Коммит с изменениями: github
Первоначальное обсуждение: http://marc.info/?t=146619199100001
Обсуждение RFC: http://marc.info/?t=146868366400003


Внимание. Если вы пишете приложения, к примеру, только на Twig и Symfony, или последние N лет делаете только API, то большая просьба не голосовать, а то получится нерепрезентативная выборка. Для вас сделан третий опрос. В первых двух интересует мнение людей, которые реально с этим работают.

Аргументированные комментарии за и против приветствуются.
Only registered users can participate in poll. Log in, please.
Насколько часто вы работаете с проектами с рендерингом шаблонов на PHP, в которых не используются шаблонизаторы?
37.23% Постоянно 223
23.04% Довольно часто 138
17.53% Довольно редко 105
22.2% Почти никогда 133
599 users voted. 138 users abstained.
Only registered users can participate in poll. Log in, please.
Как вы считаете, такой оператор был бы полезен?
58.78% Да 348
41.22% Нет 244
592 users voted. 155 users abstained.
Only registered users can participate in poll. Log in, please.
Я не использую рендеринг шаблонов на PHP…
48.67% и я считаю, что такой оператор не нужен 165
51.33% но я считаю, что такой оператор пригодится 174
339 users voted. 326 users abstained.
Tags:
Hubs:
+10
Comments 149
Comments Comments 149

Articles