HTML-сущность — это способ записать символ так, чтобы браузер показал его как текст, а не воспринял как разметку. Поставьте в содержимом обычный < — и браузер начнёт читать тег; напишите вместо него < — и на странице отрендерится буквальный <. На этой подмене и держится всё кодирование HTML-сущностей.
Пять символов несут в HTML особое значение и экранируются чаще всего: <, >, &, " и '. Экранируют их по двум причинам. Первая — отображение: нужно показать код или разметку как текст. Вторая, и более важная, — безопасность: экранирование недоверенного ввода лежит в основе защиты от межсайтового скриптинга (XSS).
Любую сущность можно записать тремя взаимозаменяемыми способами — именованным (<), десятичным (<) и шестнадцатеричным (<), — и все они дают один и тот же символ. Более тонкий вопрос — когда экранировать и чем, ведь правильный ответ зависит от того, куда попадает значение: в текст HTML, в атрибут, в скрипт или в URL. Это руководство разбирает нотации, зарезервированный набор, матрицу решений по контексту и подводные камни, которые подводят чаще всего.
Что такое HTML-сущность? (анатомия)
HTML-сущность, она же ссылка на символ (character reference), — это короткий код, заменяющий один символ. Каждая сущность начинается с амперсанда & и заканчивается точкой с запятой ;. То, что стоит между ними, и определяет, какой символ вы получите.
Существуют три формы:
&name;— именованная ссылка, например<или©.&#decimal;— десятичная числовая ссылка, например<.&#xhex;— шестнадцатеричная числовая ссылка, например<.
Браузер читает ссылку, находит символ, на который она указывает, и рендерит этот единственный символ. Видимый результат при этом не меняется — < и обычный < отображаются одинаково. Разница лишь в том, что сущность трактуется как текст, а не как начало тега.
Три нотации: именованная, десятичная, шестнадцатеричная
Все три нотации ссылаются на одну и ту же кодовую точку Unicode; различаются они только написанием. Именованная сущность — это читаемая форма, но она существует лишь для символов, у которых есть определённое имя. Десятичная сущность записывает кодовую точку в основании 10. Шестнадцатеричная сущность записывает ту же кодовую точку в основании 16, что напрямую соответствует нотации U+XXXX, которую вы видите в стандарте Unicode.
| Символ | Именованная | Десятичная | Hex |
|---|---|---|---|
< | < | < | < |
& | & | & | & |
© | © | © | © |
é | é | é | é |
Поскольку hex напрямую отражает U+XXXX — é это U+00E9, отсюда é, — многие разработчики выбирают его, когда документируют конкретную кодовую точку или рассуждают о ней. Для повседневной разметки лучше всего читаются именованные сущности.
Пять зарезервированных символов, которые нужно экранировать
Это те HTML-символы со специальным значением, которые меняют то, как браузер разбирает документ. Если такой символ оказывается в содержимом, которое нужно показать, а не выполнить, экранируйте его.
| Символ | Именованная | Десятичная | Hex | Что сломается без экранирования |
|---|---|---|---|---|
< | < | < | < | Начинает тег — браузер читает следующий текст как разметку |
> | > | > | > | Преждевременно закрывает тег |
& | & | & | & | Начинает сущность — остаток может быть прочитан как ссылка |
" | " | " | " | Рано завершает значение атрибута в двойных кавычках |
' | ' | ' | ' | Рано завершает значение атрибута в одинарных кавычках |
HTML-сущность амперсанда — корень всей системы. Символ & начинает каждую сущность, поэтому экранировать его нужно первым: экранируйте угловые скобки раньше амперсанда — и вы повторно экранируете & в только что созданных сущностях. Подробнее об этой ловушке ниже.
Когда экранировать действительно нужно? (с учётом контекста)
Именно здесь живёт большинство багов и большинство уязвимостей. Главный принцип краток: экранируйте на этапе вывода, согласованно с контекстом, куда попадает значение. То, что безопасно в одном месте, опасно в другом, поэтому применяемое кодирование обязано соответствовать месту назначения.
Содержимое HTML-элемента
Когда вы помещаете значение между тегами — внутрь <p>, <div>, <td> — экранируйте <, > и &. Экранировать здесь кавычки безвредно, но не нужно. Если требуется показать текст <strong> как буквальные символы, а не сделать следующее слово жирным, закодируйте его в <strong> — и браузер напечатает тег, а не применит его.
Значения атрибутов HTML
Внутри атрибута критическими становятся символы кавычек. Если значение стоит в title="…" и содержит неэкранированную ", оно рано завершит атрибут и позволит атакующему дописать новые атрибуты — классический вектор XSS. Экранируйте " (и желательно ') в контексте атрибута. Значение вроде He said "hi" должно стать He said "hi", чтобы остаться внутри атрибута.
Внутри <script> или встроенного JavaScript
HTML-сущности здесь не помогут. Строке, встраиваемой в блок <script> или во встроенный обработчик события, нужно экранирование строк JavaScript или JSON, а не ссылки на символы. Запись " внутри строкового литерала JS даёт буквальные шесть символов, а не кавычку. Для этого контекста используйте инструмент Экранирование JSON и прочитайте полное руководство по экранированию строк JSON про правила \uXXXX, которые действительно применимы внутри скрипта.
Внутри URL
У URL своя схема экранирования: процентное кодирование. HTML-сущности не сделают значение безопасным для URL. Строка a&b c должна попасть в запрос как a%26b%20c, а не a&b c — пробел всё равно сломает URL, а & всё так же разделит параметры. Используйте URL декодер и кодировщик для этого и руководство по кодированию и декодированию URL — для полных правил о зарезервированных и незарезервированных символах.
Матрица решений
| Контекст | Чем экранировать | Пример | Неверный выбор, который не сработает |
|---|---|---|---|
| Содержимое HTML-элемента | HTML-сущности (< > &) | <strong> → <strong> | Оставленный сырой < внедряет тег |
| Значение атрибута HTML | HTML-сущности (" ' критичны) | "hi" → "hi" | Неэкранированная " вырывается наружу |
<script> / встроенный JS | Экранирование строк JS / JSON | " → \" | HTML-сущности в JS бездействуют |
| URL / строка запроса | Процентное кодирование | пробел → %20 | & и сущности всё равно ломают URL |
Именованные или числовые: что выбрать?
Именованные сущности читаемы и являются правильным выбором по умолчанию для распространённых зарезервированных символов и хорошо известных знаков — <, &, ©, —. Однако существуют они лишь для символов, у которых есть определённое имя. Числовые сущности, десятичные или hex, могут закодировать любую кодовую точку, в том числе безымянную, что делает их универсальным запасным вариантом. Когда нельзя гарантировать, что принимающая система поддерживает конкретную именованную сущность, числовая форма — безопасный выбор.
Почему апостроф это ', а не '
Именованная сущность ' появилась только в HTML5 и XML. В HTML4 она не определена, поэтому горстка старых парсеров и почтовых клиентов рендерит её как буквальный текст ' вместо апострофа. Числовая ссылка ' — и её десятичный близнец ' — указывает на тот же самый символ, U+0027, и понятна любому соответствующему стандарту парсеру за всю историю. Хорошо протестированные библиотеки экранирования вроде he выдают ' для одинарной кавычки именно по этой причине, и хороший кодировщик следует этому соглашению, чтобы вывод можно было безопасно вставить в любой контекст HTML, XML или атрибута.
Кодировка против сущностей: когда кодировать не-ASCII
Кодировка символов, например UTF-8, определяет, как символы хранятся в виде байтов. Сущность — это способ записать символ, используя только чистый ASCII (&, #, ;, буквы, цифры). Это разные слои, и их смешение приводит к ненужному кодированию.
На странице в UTF-8 — то есть почти на любой современной странице, которая объявляет <meta charset="utf-8">, — буквы с диакритикой, тире и эмодзи являются допустимыми сырыми символами. Оставьте é, — и 😀 ровно как есть. Кодирование всего подряд в сущности имеет смысл лишь тогда, когда текст должен пережить устаревшую однобайтовую кодировку или систему, которая искажает сырой UTF-8; для таких случаев существует режим «кодировать всё не-ASCII». Если вы не уверены, как связаны байты, кодовые точки и символы, руководство по кодированию UTF-8, UTF-16 и Unicode раскладывает эту модель по полочкам.
Частые ошибки с HTML-сущностями
Экранирование & в последнюю очередь приводит к двойному экранированию
Порядок имеет значение. Если заменить < и > раньше &, у только что созданных сущностей (<, >) тоже экранируется ведущий &, так что < превращается в &lt; и рендерится как буквальный текст <. Всегда экранируйте & первым, а затем остальное. Одно это правило снимает самый частый баг кодирования.
Двойное кодирование уже экранированного текста
Прогон уже экранированного текста через кодировщик ещё раз кодирует его повторно. & становится &amp;, и посетитель видит на странице & вместо &. Экранируйте ровно один раз, на этапе вывода. Если значение проходит через несколько слоёв, убедитесь, что экранирует только один из них.
Mojibake при декодировании
У обратного направления своя ловушка. Декодируйте не той кодировкой или декодируйте дважды — и вы получите искажённый вывод, тот самый mojibake. Если страница показывает буквальный &lt; там, где вы ожидали <, вставьте его в HTML-декодировщик сущностей, чтобы точно увидеть, во что разворачиваются сущности; он обрабатывает именованные, десятичные, hex и даже устаревшие незавершённые ссылки вроде © без завершающей точки с запятой.
Вера в экранирование как в полное лекарство от XSS
Экранирование — первая линия обороны, а не единственная. Поскольку у HTML есть несколько контекстов с разными правилами, экранирование под не тот контекст оставляет дыру — кавычки в атрибутах, экранирование JS в скрипте, процентное кодирование в URL. Сочетайте корректное экранирование с учётом контекста с политикой безопасности контента (Content Security Policy) и автоэкранированием вашего фреймворка. Считайте кодирование сущностей фундаментом, поверх которого наслаиваются CSP и настройки фреймворка по умолчанию.
Как кодировать и декодировать сущности на практике
Когда вы собираете HTML вручную, экранируете его вы сами. Вот корректная escapeHtml(), которая соблюдает порядок «& первым», плюс более правильный подход для реального кода приложения.
// The five reserved characters and their safe entities:
// < → < > → > & → & " → " ' → '
function escapeHtml(str) {
return str
.replace(/&/g, '&') // & FIRST, so later entities are not double-escaped
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, '''); // numeric form — safe in HTML4, HTML5 and XML
}
const userInput = `<a href="x">Tom & Jerry's</a>`;
const safe = escapeHtml(userInput);
// → <a href="x">Tom & Jerry's</a>
// Better in app code: let the platform escape for you.
// el.textContent = userInput; // the browser escapes; no manual replace
// React / Vue / Angular escape interpolated text by default
// Server templates (Jinja, ERB, Blade) auto-escape unless you opt out
Самописная функция полезна, чтобы понять происходящее, и для разовых преобразований, но в продакшене предпочтительнее встроенный путь. Установка element.textContent даёт браузеру экранировать за вас, а современные фреймворки автоматически экранируют интерполируемые значения. Оставьте ручное экранирование для случаев, которые платформа не покрывает.
Для разовых задач HTML-кодировщик сущностей экранирует зарезервированный набор (именованно, десятично или hex), а HTML-декодировщик сущностей выполняет обратное. Эти двое — точные инверсии друг друга для зарезервированных символов, так что можно прогнать текст через оба туда и обратно без потерь.
Часто задаваемые вопросы
Что такое HTML-сущность?
HTML-сущность — это короткий код, начинающийся с & и заканчивающийся ;, который представляет один символ. Браузер рендерит символ, на который указывает сущность, вместо того чтобы воспринимать её как разметку. Например, < отображает буквальный <, а & — буквальный &.
Какие символы нужно экранировать в HTML?
Пять зарезервированных HTML-символов со специальным значением: <, >, &, " и '. В содержимом элемента в основном нужны <, > и &; в значениях атрибутов критичными становятся ещё и кавычки " и '. Экранируйте амперсанд & первым, чтобы остальные сущности не были экранированы дважды.
Использовать именованные или числовые (десятичные/hex) сущности?
Используйте именованные сущности (<, ©) ради читаемости с распространёнными символами, поскольку их легко узнать. Используйте числовые сущности (десятичную < или hex <), когда нужно закодировать символ без определённого имени или когда нельзя гарантировать, что потребитель поддерживает данную именованную сущность. Обе формы ссылаются на одну и ту же кодовую точку.
Защищают ли HTML-сущности от XSS?
Они являются фундаментом, если применять их правильно. Экранирование пяти зарезервированных символов перед помещением недоверенного ввода в содержимое HTML-элемента или атрибута останавливает внедрение тегов и скриптов. Но экранирование зависит от контекста: блокам скриптов нужно экранирование JavaScript, а URL — процентное кодирование. Сочетайте корректное экранирование с учётом контекста с CSP и автоэкранированием фреймворка.
Почему моя страница показывает &lt; вместо <?
Это двойное экранирование. Текст был закодирован дважды, либо & был экранирован после угловых скобок, так что & в < превратился в &. Посетитель тогда видит < как буквальный текст. Экранируйте ровно один раз и всегда экранируйте & первым. Инструмент-декодировщик может подтвердить, во что разворачиваются сущности.
Нужно ли экранировать символы вроде é, — или эмодзи?
Обычно нет. На странице, которая объявляет <meta charset="utf-8">, буквы с диакритикой, тире и эмодзи являются допустимыми сырыми символами и не требуют кодирования — оставьте их как есть. Кодируйте не-ASCII лишь тогда, когда текст должен пройти через устаревшую однобайтовую кодировку или систему, которая портит сырой UTF-8.