HTML-сущности: когда и зачем экранировать символы
Что такое HTML-сущности, какие символы экранировать, как кодирование защищает от XSS и как кодировать и декодировать их прямо в браузере.
Когда я впервые сломал страницу неэкранированным <, я минут двадцать смотрел на пустое поле комментария и не мог понять, куда делся текст. Браузер прочитал < как начало тега и молча выкинул всё, что шло после if (x < 10). Ради этого случая HTML-сущности и придумали.
Что такое HTML-сущность
Некоторые символы значат что-то для браузера раньше, чем для читателя. < открывает тег. & начинает сущность. " закрывает значение атрибута. Если вы хотите, чтобы такой символ просто появился на экране, его меняют на код, который браузер выводит буквально.
Форм три:
- Именованные:
<,&,©— читаемые, но набор фиксированный. - Десятичные:
<,&— номер символа в Unicode. - Шестнадцатеричные:
<,&— тот же номер в системе с основанием 16.
Все три рисуют одно и то же. <, < и < выведут на экран <.
Пять символов, которые пропускать нельзя
В повседневном HTML достаточно помнить про пять символов:
&→&<→<>→>"→"'→'
И здесь важен порядок — на нём спотыкаются чаще всего. Амперсанд экранируют первым. Если сначала заменить < на <, а потом пойти искать одиночные &, вы повторно закодируете только что созданный амперсанд и получите &lt; — на экране будет текст <, а не скобка. HTML-кодировщик обрабатывает амперсанд первым, так что этой ловушки не будет.
Это про безопасность, а не только про отображение
Экранирование — не косметика. Это первая линия обороны от XSS.
Допустим, сайт показывает комментарии. Кто-то вводит в поле <script>fetch('/api/keys')</script>. Если вставить это в страницу как есть, браузер увидит не текст, а тег скрипта — и выполнит его в сессии следующего посетителя, с его куками. Экранируйте < и >, и тот же ввод станет безобидным текстом <script>..., ничего больше.
React и Vue экранируют вывод по умолчанию, поэтому в обычной работе об этом почти не думаешь. Но стоит тронуть innerHTML, собрать HTML-строку руками или писать сырые шаблоны — и страховки больше нет, экранирование снова на вас.
Декодирование и трюк с не-ASCII
Декодирование работает в обратную сторону: вставьте Рыба & чипсы — £5 и получите Рыба & чипсы — £5. Пригодится, когда читаешь спарсенную страницу, RSS-ленту или ответ API, который пришёл уже закодированным и нечитаемым.
Ещё есть флажок — кодировать всё за пределами обычного ASCII: буквы с акцентами, кириллицу, иероглифы, эмодзи — в числовые сущности вроде 😀 для 😀. Нужен он нечасто. Зато выручает, когда система не переваривает ничего, кроме 7-битного ASCII, а таких старых почтовых шаблонов и полей в древних CMS до сих пор хватает.
И одно скажу прямо: всё считается в браузере. Вставьте конфиг или комментарий клиента, полный разметки, — ни один символ не уйдёт с вашей машины.
Проверьте на своём следующем упрямом фрагменте: HTML-кодировщик и декодировщик кодирует и декодирует в одном поле, мгновенно.