Skip to content
Powrót do bloga
Poradniki

Encje HTML wyjaśnione: nazwane, liczbowe i kiedy je escapować

Praktyczny przewodnik po encjach HTML: odwołania nazwane, dziesiętne i szesnastkowe, pięć znaków do zescapowania oraz reguły kontekstu chroniące przed XSS.

8 min czytania

Encja HTML to sposób zapisania znaku tak, aby przeglądarka pokazała go jako tekst, zamiast traktować jako znaczniki. Wpisz surowy < w treści, a przeglądarka zacznie czytać tag; napisz zamiast tego &lt;, a na stronie pojawi się dosłowny <. Ta zamiana to cała idea kodowania encji HTML.

Pięć znaków ma w HTML specjalne znaczenie i to właśnie je escapuje się najczęściej: <, >, &, " oraz '. Robi się to z dwóch powodów. Pierwszy to wyświetlanie — chcesz pokazać kod lub znaczniki jako tekst. Drugi, ważniejszy, to bezpieczeństwo: escapowanie niezaufanych danych wejściowych jest fundamentem obrony przed atakami typu cross-site scripting (XSS).

Każdą encję można zapisać na trzy wymienne sposoby — nazwany (&lt;), dziesiętny (&#60;) i szesnastkowy (&#x3C;) — a wszystkie rozwijają się do tego samego znaku. Trudniejsze pytanie brzmi: kiedy escapować i czym, bo właściwa odpowiedź zależy od tego, gdzie trafia wartość: do tekstu HTML, atrybutu, skryptu czy URL-a. Poniżej notacje, zbiór znaków zarezerwowanych, macierz decyzyjna kontekstów oraz pułapki, które najczęściej dają się we znaki.

Czym jest encja HTML? (anatomia)

Encja HTML, nazywana też odwołaniem znakowym, to krótki kod, który zastępuje pojedynczy znak. Każda encja zaczyna się od ampersanda & i kończy średnikiem ;. To, co znajduje się pomiędzy, decyduje o tym, jaki znak otrzymasz.

Istnieją trzy postacie:

  • &name; — odwołanie nazwane, jak &lt; czy &copy;.
  • &#decimal; — liczbowe odwołanie dziesiętne, jak &#60;.
  • &#xhex; — liczbowe odwołanie szesnastkowe, jak &#x3C;.

Przeglądarka czyta odwołanie, odszukuje znak, na który ono wskazuje, i renderuje ten pojedynczy znak. W widocznym efekcie nic się nie zmienia — &lt; i surowy < wyglądają identycznie. Jedyna różnica polega na tym, że encja jest traktowana jako tekst, a nigdy jako początek tagu.

Trzy notacje: nazwana, dziesiętna, szesnastkowa

Wszystkie trzy notacje odwołują się do tego samego punktu kodowego Unicode; różnią się jedynie zapisem. Encja nazwana to forma czytelna, ale istnieje tylko dla znaków, które mają zdefiniowaną nazwę. Encja dziesiętna zapisuje punkt kodowy w systemie dziesiętnym. Encja szesnastkowa zapisuje ten sam punkt kodowy w systemie szesnastkowym, który odpowiada jeden do jednego notacji U+XXXX znanej ze standardu Unicode.

ZnakNazwanaDziesiętnaSzesnastkowa
<&lt;&#60;&#x3C;
&&amp;&#38;&#x26;
©&copy;&#169;&#xA9;
é&eacute;&#233;&#xE9;

Ponieważ zapis szesnastkowy bezpośrednio odzwierciedla U+XXXXé to U+00E9, stąd &#xE9; — wielu programistów sięga po niego przy dokumentowaniu konkretnego punktu kodowego lub analizowaniu go. Do codziennych znaczników najlepiej czytają się encje nazwane.

Pięć znaków zarezerwowanych, które musisz escapować

To są specjalne znaki HTML, które zmieniają sposób, w jaki przeglądarka parsuje dokument. Jeśli któryś z nich pojawi się w treści, która ma być wyświetlona, a nie wykonana — zescapuj go.

ZnakNazwanaDziesiętnaSzesnastkowaCo się psuje, gdy nie escapujesz
<&lt;&#60;&#x3C;Rozpoczyna tag — przeglądarka czyta dalszy tekst jako znaczniki
>&gt;&#62;&#x3E;Przedwcześnie zamyka tag
&&amp;&#38;&#x26;Rozpoczyna encję — reszta może zostać błędnie odczytana jako odwołanie
"&quot;&#34;&#x22;Za wcześnie kończy wartość atrybutu w cudzysłowie podwójnym
'&#x27;&#39;&#x27;Za wcześnie kończy wartość atrybutu w cudzysłowie pojedynczym

Encja ampersanda HTML stoi u podstaw całego systemu. Znak & rozpoczyna każdą encję, więc trzeba go escapować jako pierwszy — zescapuj nawiasy ostre przed ampersandem, a ponownie zescapujesz & w encjach, które dopiero co utworzyłeś. Wracam do tej pułapki niżej.

Kiedy faktycznie trzeba escapować? (zależnie od kontekstu)

Stąd bierze się większość błędów i większość podatności. Główna zasada jest krótka: escapuj w momencie wyjścia, dopasowując kodowanie do kontekstu, w którym ląduje wartość. Ta sama wartość bywa bezpieczna w treści HTML, a groźna w atrybucie albo w skrypcie.

Treść elementu HTML

Gdy wstawiasz wartość pomiędzy tagi — wewnątrz <p>, <div>, <td> — escapuj <, > i &. Escapowanie cudzysłowów jest tu nieszkodliwe, ale zbędne. Jeśli chcesz pokazać tekst <strong> jako dosłowne znaki, zamiast pogrubić kolejne słowo, zakoduj go do &lt;strong&gt;, a przeglądarka wypisze tag, zamiast go zastosować.

Wartości atrybutów HTML

Wewnątrz atrybutu kluczowe stają się znaki cudzysłowu. Jeśli wartość znajduje się w title="…" i zawiera niezescapowany ", kończy ona atrybut za wcześnie i pozwala atakującemu dopisać nowe atrybuty — klasyczny wektor XSS. W kontekście atrybutu escapuj " (a najlepiej również '). Wartość taka jak He said "hi" musi stać się He said &quot;hi&quot;, by pozostać w ryzach.

Wewnątrz <script> lub JavaScriptu w treści

Encje HTML tu nie pomagają. Łańcuch znaków budowany w bloku <script> lub w atrybucie obsługi zdarzeń wymaga escapowania łańcuchów JavaScriptu lub JSON, a nie odwołań znakowych. Napisanie &quot; wewnątrz literału łańcucha JS daje dosłowne sześć znaków, a nie cudzysłów. W tym kontekście sięgnij po narzędzie Escape JSON, a reguły \uXXXX, które faktycznie obowiązują wewnątrz skryptu, znajdziesz w kompletnym przewodniku po escapowaniu łańcuchów JSON.

Wewnątrz URL-a

URL ma własny schemat escapowania: kodowanie procentowe. Encje HTML nie sprawią, że wartość będzie bezpieczna w URL-u. Łańcuch a&b c należy w zapytaniu zapisać jako a%26b%20c, a nie a&amp;b c — spacja wciąż psuje URL, a & wciąż rozdziela parametry. Użyj do tego Kodera i dekodera URL, a pełne reguły dotyczące znaków zarezerwowanych i niezarezerwowanych opisuje przewodnik po kodowaniu i dekodowaniu URL.

Macierz decyzyjna

KontekstEscapuj za pomocąPrzykładBłędny wybór, który zawodzi
Treść elementu HTMLencje HTML (< > &)<strong>&lt;strong&gt;Pozostawienie surowego < wstrzykuje tag
Wartość atrybutu HTMLencje HTML (" ' kluczowe)"hi"&quot;hi&quot;Niezescapowany " wyrywa się z atrybutu
<script> / JS w treściescapowanie łańcuchów JS / JSON"\"Encje HTML są bezczynne w JS
URL / query stringkodowanie procentowespacja → %20&amp; i encje nadal psują URL

Nazwane czy liczbowe: których użyć?

Encje nazwane są czytelne i stanowią właściwy domyślny wybór dla typowych znaków zarezerwowanych oraz dobrze znanych symboli — &lt;, &amp;, &copy;, &mdash;. Istnieją one jednak tylko dla znaków mających zdefiniowaną nazwę. Encje liczbowe, dziesiętne lub szesnastkowe, potrafią zakodować dowolny punkt kodowy, także taki bez nazwy, co czyni je uniwersalnym rozwiązaniem awaryjnym. Gdy nie możesz zagwarantować, że odbierający system obsługuje daną encję nazwaną, bezpiecznym wyborem jest forma liczbowa.

Dlaczego apostrof to &#x27;, a nie &apos;

Encja nazwana &apos; została wprowadzona dopiero w HTML5 i XML. Jest niezdefiniowana w HTML4, więc garstka starszych parserów i klientów poczty renderuje ją jako dosłowny tekst &apos; zamiast apostrofu. Odwołanie liczbowe &#x27; — oraz jego dziesiętny bliźniak &#39; — wskazuje dokładnie ten sam znak, U+0027, i jest rozumiane przez każdy zgodny ze specyfikacją parser, jaki kiedykolwiek napisano. Dobrze przetestowane biblioteki escapujące, takie jak he, emitują &#x27; dla cudzysłowu pojedynczego właśnie z tego powodu, a dobry koder trzyma się tej konwencji, by wynik dało się bezpiecznie wstawić w dowolny kontekst HTML, XML lub atrybutu.

Charset kontra encje: kiedy kodować znaki spoza ASCII

Zestaw znaków, taki jak UTF-8, decyduje o tym, jak znaki są przechowywane w postaci bajtów. Encja to sposób na zapisanie znaku przy użyciu wyłącznie zwykłego ASCII (&, #, ;, liter, cyfr). To różne warstwy, a ich mylenie prowadzi do niepotrzebnego kodowania.

Na stronie w UTF-8 — czyli niemal każdej nowoczesnej stronie deklarującej <meta charset="utf-8"> — litery akcentowane, myślniki i emoji są poprawnymi surowymi znakami. Zostaw é, i 😀 dokładnie tak, jak są. Kodowanie wszystkiego do encji ma znaczenie tylko wtedy, gdy tekst musi przetrwać starszy jednobajtowy zestaw znaków lub system, który przekłamuje surowy UTF-8; do takich przypadków istnieje tryb „zakoduj wszystkie znaki spoza ASCII”. Jeśli nie masz pewności, jak mają się do siebie bajty, punkty kodowe i znaki, model ten wykłada przewodnik po kodowaniu UTF-8, UTF-16 i Unicode.

Najczęstsze pułapki encji HTML

Escapowanie & na końcu powoduje podwójne escapowanie

Kolejność ma znaczenie. Jeśli zamienisz < i > przed &, to encje, które właśnie utworzyłeś (&lt;, &gt;), również dostaną zescapowany wiodący &, więc < skończy jako &amp;lt; i wyrenderuje się jako dosłowny tekst &lt;. Zawsze escapuj & jako pierwszy, a potem resztę. Ta jedna reguła oszczędza najczęstszego błędu kodowania.

Podwójne kodowanie tekstu już zescapowanego

Przepuszczenie przez koder tekstu, który jest już zescapowany, koduje go ponownie. &amp; staje się &amp;amp;, a odwiedzający widzi na stronie &amp; zamiast &. Escapuj dokładnie raz, w momencie wyjścia. Jeśli wartość przechodzi przez kilka warstw, dopilnuj, by tylko jedna z nich escapowała.

Mojibake przy dekodowaniu

Droga w drugą stronę ma własną pułapkę. Zdekoduj złym zestawem znaków albo zdekoduj dwukrotnie, a otrzymasz zniekształcony wynik — klasyczne mojibake. Jeśli strona pokazuje dosłowne &amp;lt; tam, gdzie spodziewałeś się <, wklej to do Dekodera encji HTML, aby zobaczyć, do czego dokładnie rozwijają się encje; obsługuje on formy nazwane, dziesiętne, szesnastkowe, a nawet przestarzałe, niedomknięte odwołania w rodzaju &copy bez kończącego średnika.

Traktowanie escapowania jako kompletnego lekarstwa na XSS

Escapowanie to pierwsza linia obrony, a nie jedyna. Ponieważ HTML ma kilka kontekstów o różnych regułach, escapowanie pod niewłaściwy z nich zostawia lukę — cudzysłowy w atrybutach, escapowanie JS w skrypcie, kodowanie procentowe w URL-ach. Poprawne, świadome kontekstu escapowanie połącz z Content Security Policy oraz automatycznym escapowaniem swojego frameworka.

Jak kodować i dekodować encje w praktyce

Gdy budujesz HTML ręcznie, escapujesz go samodzielnie. Oto poprawna funkcja escapeHtml(), która radzi sobie z kolejnością „& najpierw”, a także lepsza praktyka dla prawdziwego kodu aplikacji.

// Pięć znaków zarezerwowanych i ich bezpieczne encje:
//   <  →  &lt;     >  →  &gt;     &  →  &amp;     "  →  &quot;     '  →  &#x27;

function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')   // & NAJPIERW, by późniejsze encje nie były podwójnie zescapowane
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;'); // forma liczbowa — bezpieczna w HTML4, HTML5 i XML
}

const userInput = `<a href="x">Tom & Jerry's</a>`;
const safe = escapeHtml(userInput);
// → &lt;a href=&quot;x&quot;&gt;Tom &amp; Jerry&#x27;s&lt;/a&gt;

// Lepiej w kodzie aplikacji: pozwól platformie escapować za ciebie.
//   el.textContent = userInput;   // przeglądarka escapuje; bez ręcznego replace
//   React / Vue / Angular domyślnie escapują interpolowany tekst
//   Szablony serwerowe (Jinja, ERB, Blade) escapują automatycznie, chyba że się z tego wypiszesz

Funkcja napisana ręcznie przydaje się do zrozumienia, co się dzieje, oraz do jednorazowych konwersji, ale na produkcji wybieraj wbudowaną ścieżkę. Ustawienie element.textContent pozwala przeglądarce escapować za ciebie, a nowoczesne frameworki escapują interpolowane wartości automatycznie. Ręczne escapowanie zostaw na przypadki, których platforma nie obejmuje.

Do pracy doraźnej Koder encji HTML escapuje zbiór zarezerwowany (nazwany, dziesiętny lub szesnastkowy), a Dekoder encji HTML odwraca operację. Oba są dokładnymi odwrotnościami dla znaków zarezerwowanych, więc tekst da się przepuścić przez nie tam i z powrotem bez strat.

Najczęściej zadawane pytania

Czym jest encja HTML?

Encja HTML to krótki kod, zaczynający się od & i kończący ;, który reprezentuje pojedynczy znak. Przeglądarka renderuje znak, na który encja wskazuje, zamiast traktować ją jako znaczniki. Na przykład &lt; wyświetla dosłowny <, a &amp; wyświetla dosłowny &.

Które znaki muszę escapować w HTML?

Pięć zarezerwowanych specjalnych znaków HTML: <, >, &, " oraz '. W treści elementu potrzebujesz głównie <, > i &; w wartościach atrybutów kluczowe stają się również cudzysłowy " i '. Escapuj ampersand & jako pierwszy, by pozostałe encje nie zostały podwójnie zescapowane.

Czy używać encji nazwanych, czy liczbowych (dziesiętnych/szesnastkowych)?

Encji nazwanych (&lt;, &copy;) używaj dla czytelności przy typowych znakach, bo łatwo je rozpoznać. Encji liczbowych (dziesiętnej &#60; lub szesnastkowej &#x3C;) używaj, gdy musisz zakodować znak bez zdefiniowanej nazwy albo gdy nie możesz zagwarantować, że odbiorca obsłuży daną encję nazwaną. Obie formy odwołują się do tego samego punktu kodowego.

Czy encje HTML chronią przed XSS?

Są fundamentem, o ile zastosujesz je poprawnie. Zescapowanie pięciu zarezerwowanych znaków przed umieszczeniem niezaufanych danych wejściowych w treści elementu lub atrybutu HTML powstrzymuje wstrzyknięcie tagów i skryptów. Ale escapowanie zależy od kontekstu: bloki skryptów wymagają escapowania JavaScriptu, a URL-e kodowania procentowego. Połącz poprawne, świadome kontekstu escapowanie z CSP i automatycznym escapowaniem frameworka.

Dlaczego moja strona pokazuje &amp;lt; zamiast <?

To podwójne escapowanie. Tekst został zakodowany dwukrotnie albo & zescapowano po nawiasach ostrych, więc & w &lt; zamieniło się w &amp;. Odwiedzający widzi wtedy &lt; jako dosłowny tekst. Escapuj dokładnie raz i zawsze escapuj & jako pierwszy. Narzędzie dekodujące potwierdzi, do czego rozwijają się encje.

Czy muszę escapować znaki takie jak é, — lub emoji?

Zwykle nie. Na stronie deklarującej <meta charset="utf-8"> litery akcentowane, myślniki i emoji są poprawnymi surowymi znakami i nie wymagają kodowania — zostaw je tak, jak są. Koduj znaki spoza ASCII tylko wtedy, gdy tekst musi przejść przez starszy jednobajtowy zestaw znaków albo system uszkadzający surowy UTF-8.

Tagi: HTML Encoding Security Web

Powiązane artykuły

Zobacz wszystkie artykuły