Кодировщик / декодер HTML-сущностей

Экранируйте и раскрывайте HTML-сущности. Преобразуйте < > & " ' в именованные сущности, при необходимости кодируйте все не-ASCII символы и раскрывайте именованные или числовые ссылки обратно в текст.

Экранируйте текст, чтобы он был безопасен внутри HTML, или превращайте сущности обратно в символы. Вводите в любое поле, чтобы конвертировать в этом направлении. Минимальный режим экранирует только пять символов, важных для корректности; расширенный режим также кодирует каждый не-ASCII символ для максимальной совместимости.

Распространённые сущности

Символ Именованная Числовая
& &amp; &#38;
< &lt; &#60;
> &gt; &#62;
" &quot; &#34;
' &#39; &#39;
\u{00A0} &nbsp; &#160;
\u{00A9} &copy; &#169;
\u{20AC} &euro; &#8364;

HTML-сущности и зачем важно экранирование

Пять символов, ломающих HTML

HTML-парсер читает < как начало тега, а & как начало ссылки на сущность. Если ваш контент содержит любой из них, парсер перестаёт считать его текстом. Экранирование заменяет символ ссылкой, которую парсер разрешает обратно в исходный символ, не придавая ей структурного смысла.

Действительно нужно экранировать только пять символов:

  • & становится &amp; — должен быть первым, иначе вы дважды экранируете всё остальное
  • < становится &lt;
  • > становится &gt;
  • " становится &quot; — обязателен внутри значений атрибутов в двойных кавычках
  • ' становится &#39; — обязателен внутри значений атрибутов в одинарных кавычках

В тексте элемента строго нужны только & и <. Остальные три важны, как только ваш текст может попасть внутрь атрибута, а поскольку вы редко контролируете это в момент экранирования, экранирование всех пяти — безопасная привычка.

Именованные, десятичные и шестнадцатеричные ссылки

Один и тот же символ можно записать тремя способами. Неразрывный пробел — это &nbsp;, &#160; или &#xA0;. Именованные ссылки читаемее всего, но в списке HTML5 около 2200 записей, а старые парсеры знают куда меньше, поэтому неизвестные имена — риск переносимости. Числовые ссылки работают для любой точки кода Unicode и всегда безопасны. Каждая именованная ссылка в HTML5 требует завершающей точки с запятой; горстка устаревших имён вроде &amp всё ещё разбирается без неё для обратной совместимости, но полагаться на это — ошибка, которая рано или поздно проявится.

Экранирование — это не санитизация

Экранирование — это то, что останавливает межсайтовый скриптинг: если пользовательский ввод, содержащий <script>, экранирован в &lt;script&gt;, браузер выводит буквальный текст и ничего не выполняет. Но экранирование работает, только если применяется в правильном контексте. HTML-экранирования недостаточно внутри блока <script>, внутри атрибута style или в URL — каждый из них требует собственного кодирования. И экранирование должно применяться один раз, на выводе. Экранирование на вводе и снова на выводе даёт &amp;lt; и видимый мусор.

Когда нужен расширенный режим

Экранирование не-ASCII символов не требуется ни одной современной спецификацией. Документ в UTF-8 может содержать é и напрямую, и это обычно лучший выбор: короче, читаемее и проще для поиска. Расширенный режим существует для случаев, когда поток байтов должен пережить канал, искажающий не-ASCII — некоторые устаревшие почтовые шаблоны, старые поля CMS, XML-конвейеры с неясной объявленной кодировкой или этап сборки, застрявший в Latin-1. Если вы контролируете кодировку от начала до конца, оставайтесь в минимальном режиме.

Ловушка неразрывного пробела

&nbsp; — это U+00A0, символ, отличный от обычного пробела U+0020. Он предотвращает перенос строки и не даёт браузерам схлопывать последовательности пробелов, поэтому WYSIWYG-редакторы щедро выдают его. Поскольку он выглядит одинаково, он молча ломает сравнение строк, регулярные выражения, написанные с \s в некоторых движках, и импорт CSV. Если значение выглядит правильным, но отказывается совпадать, проверьте на случайный неразрывный пробел.

Примечание с открытым исходным кодом: реализовано на обычных операциях со строками и кодовыми точками. Сторонние библиотеки не используются.

Часто задаваемые вопросы

Нужно ли экранировать знак больше?
Строго нет — одинокий > в тексте однозначен и разбирается нормально. Его экранируют по традиции, потому что это ничего не стоит и избавляет от путаницы в сгенерированной разметке, особенно когда вывод позже обрабатывается инструментами строже браузера.
Почему & нужно экранировать первым?
Потому что каждая другая escape-последовательность начинается с амперсанда. Если вы замените < на &lt; до экранирования &, следующий проход превратит этот амперсанд в &amp;, и в итоге на странице окажется &amp;lt;.
Достаточно ли экранирования для защиты от XSS?
Только для текстового и атрибутного контекста HTML и только при применении на выводе. Контент, внедряемый в JavaScript, CSS или URL, требует правил экранирования этого языка. Экранирование — один слой, а не полная защита.
Именованные или числовые сущности — что выбрать?
Числовые ссылки поддерживаются повсеместно и работают для любой точки кода. Именованные читаемее для горстки общеизвестных. На практике используйте именованные для пяти основных символов и числовые для всего необычного.
Стоит ли кодировать буквы с диакритикой?
Не в документе UTF-8, которым по сути является весь современный веб-контент. Пишите их напрямую. Используйте расширенный режим, только если что-то в вашем конвейере не заслуживает доверия в переноске не-ASCII байтов без искажений.
В чём разница между &nbsp; и обычным пробелом?
Неразрывный пробел — это кодовая точка U+00A0. Он выглядит так же, но предотвращает перенос строки и не схлопывается с соседними пробелами. Это также отдельный символ для целей сравнения, что делает его частым источником тонких ошибок.