Zalecana metoda zmiany znaczenia kodu HTML w Javie

262

Czy jest zalecanym sposobem ucieczki <, >, "a &znaki przy wysyłaniu HTML w zwykły kod Java? (Innymi słowy niż ręczne wykonanie następujących czynności).

String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = source.replace("<", "&lt;").replace("&", "&amp;"); // ...
Ben Lings
źródło
2
Należy pamiętać, że jeśli wyprowadzasz wyjście do niecytowanego atrybutu HTML, że inne znaki, takie jak spacja, tabulator, backspace itp., Mogą pozwolić atakującym na wprowadzenie atrybutów javascript bez żadnego z wymienionych znaków. Aby uzyskać więcej informacji, zobacz Ściągawka na temat zapobiegania OWASP XSS.
Jeff Williams
BTW, w tym kodzie powinieneś wstawić znak „&” przed „<”, aby to działało poprawnie („& lt;" zamień na "& amp; lt;" w przeciwnym razie, co jest renderowane jako "& lt;", a nie "< "):source.replace("&", "&amp;").replace("<", "&lt;");
Tey '23

Odpowiedzi:

261

StringEscapeUtils z Apache Commons Lang :

import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
// ...
String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = escapeHtml(source);

Dla wersji 3 :

import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;
// ...
String escaped = escapeHtml4(source);
dfa
źródło
2
Chociaż StringEscapeUtilsjest to miłe, nie będzie poprawnie uciekał do białych znaków dla atrybutów, jeśli chcesz uniknąć normalizacji białych znaków HTML / XML. Zobacz moją odpowiedź, aby uzyskać więcej szczegółów.
Adam Gent,
21
Powyższy przykład jest zepsuty. Użyj teraz metody escapeHtml4 ().
stackoverflowuser2010
3
Dla fanów Guawy patrz odpowiedź okranza poniżej.
George Hawkins
2
Jeśli strona ma kodowanie UTF-8, to wszystko, czego potrzebujemy, to htmlEscaper Guavy, który ucieka tylko następującym pięciu znakom ASCII: „” i <>. Funkcja EscapeHtml () Apache zastępuje również znaki spoza ASCII, w tym akcenty, które wydają się niepotrzebne w sieci UTF-8 strony?
zdenekca
4
Jest teraz przestarzałe w commons-lang3. Został przeniesiony do commons.apache.org/proper/commons-text
Danny
137

Alternatywą dla Apache Commons: Redakcyjne wiosennego „s HtmlUtils.htmlEscape(String input)metody.

Adamski
źródło
9
Dzięki. Użyłem go (zamiast StringEscapeUtils.escapeHtml()z wersji apache-commons2.6), ponieważ pozostawia rosyjskie znaki bez zmian .
Slava Semushin
6
Dobrze wiedzieć. TBH W dzisiejszych czasach mam szerokie pole do Apache.
Adamski
1
Użyłem go również, pozostawia chińskie znaki takimi, jakie są.
smartwjw
Jak wypada porównanie z alternatywą guawy wymienioną poniżej?
vishvAs vAsuki
2
I koduje również apostrof, więc jest to rzeczywiście przydatne, w przeciwieństwie do apache StringEscapeUtils
David Balažic
57

Ładna krótka metoda:

public static String escapeHTML(String s) {
    StringBuilder out = new StringBuilder(Math.max(16, s.length()));
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if (c > 127 || c == '"' || c == '\'' || c == '<' || c == '>' || c == '&') {
            out.append("&#");
            out.append((int) c);
            out.append(';');
        } else {
            out.append(c);
        }
    }
    return out.toString();
}

Na podstawie https://stackoverflow.com/a/8838023/1199155 (wzmacniacza tam nie ma). Cztery znaki zaznaczone w klauzuli if są jedynymi poniżej 128, zgodnie z http://www.w3.org/TR/html4/sgml/entities.html

Bruno Eberhard
źródło
Miły. Nie używa „wersji HTML” kodowania (przykład: „á” byłoby „& aacute;” zamiast „& # 225;”), ale ponieważ te numeryczne działają nawet w IE7, myślę, że nie muszę się martwić Dzięki.
nonzaprej
Dlaczego kodujesz wszystkie te znaki, gdy OP poprosił o ucieczkę od 4 odpowiednich znaków? Marnujesz procesor i pamięć.
David Balažic,
1
Zapomniałeś apostrofu. Dzięki temu ludzie mogą wprowadzać niekwotowane atrybuty wszędzie tam, gdzie ten kod służy do zmiany wartości atrybutów.
David Balažic,
45

Istnieje nowsza wersja biblioteki Apache Commons Lang i używa innej nazwy pakietu (org.apache.commons.lang3). StringEscapeUtilsMa teraz różne metody statyczne do ucieczki różnych typów dokumentów ( http://commons.apache.org/proper/commons-lang/javadocs/api-3.0/index.html ). Aby uniknąć łańcucha znaków HTML w wersji 4.0:

import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;

String output = escapeHtml4("The less than sign (<) and ampersand (&) must be escaped before using them in HTML");
Martin Dimitrov
źródło
3
Niestety nic nie istnieje dla HTML 5, a dokumenty Apache nie określają, czy właściwe jest użycie escapeHtml4 dla HTML 5.
Paul Vincent Craven
43

Dla tych, którzy korzystają z Google Guava:

import com.google.common.html.HtmlEscapers;
[...]
String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = HtmlEscapers.htmlEscaper().escape(source);
okrasz
źródło
40

Na Androidzie (API 16 lub nowszy) możesz:

Html.escapeHtml(textToScape);

lub dla niższego API:

TextUtils.htmlEncode(textToScape);
OriolJ
źródło
Czy istnieje jakikolwiek powód do użycia escapeHtmlzamiast htmlEncode?
Muz
2
Zobacz także moje pytanie dotyczące różnicy między tymi dwoma. (@Muz)
JonasCz - Przywróć Monikę
37

Uważaj na to. W dokumencie HTML istnieje wiele różnych „kontekstów”: wewnątrz elementu, cytowanej wartości atrybutu, niecytowanej wartości atrybutu, atrybutu URL, javascript, CSS itp. Dla każdej z tych opcji należy użyć innej metody kodowania te, aby zapobiec skryptom krzyżowym (XSS). Sprawdź arkusza OWASP XSS Zapobieganie Cheat szczegóły na każdym z tych kontekstów. Metody ucieczki dla każdego z tych kontekstów można znaleźć w bibliotece OWASP ESAPI - https://github.com/ESAPI/esapi-java-legacy .

Jeff Williams
źródło
6
DZIĘKUJEMY za wskazanie, że kontekst, w którym chcesz zakodować dane wyjściowe, ma bardzo duże znaczenie. Termin „kodowanie” jest również o wiele bardziej odpowiednim czasownikiem niż „ucieczka”. Ucieczka implikuje jakąś specjalną siekać, w przeciwieństwie do „jak mogę zakodować ten ciąg do: atrybut XHTML / SQL kwerenda parametryczna / PostScript print ciąg pole wyjściowe / CSV
Roboprog
5
„Kodowanie” i „ucieczka” są powszechnie używane do opisania tego. Termin „ucieczka” jest na ogół używany, gdy proces polega na dodaniu „znaku ucieczki” przed znakiem stosownym pod względem składniowym, takim jak ucieczka znaku cudzysłowu ukośnikiem odwrotnym \ „Termin„ kodowanie ”jest częściej używany podczas tłumaczenia znak w innej formie, np. URL kodujący znak cudzysłowu% 22 lub kod HTML encji jako & # x22 lub @ quot.
Jeff Williams
1
Aby zaoszczędzić trochę googlingu, poszukaj klasy Encoder static.javadoc.io/org.owasp.esapi/esapi/2.0.1/org/owasp/esapi/…
Jakub Bochenski
14

Do niektórych celów HtmlUtils :

import org.springframework.web.util.HtmlUtils;
[...]
HtmlUtils.htmlEscapeDecimal("&"); //gives &#38;
HtmlUtils.htmlEscape("&"); //gives &amp;
AUU
źródło
1
Od wiosny HtmlUtils komentuje: * <p> Aby uzyskać kompleksowy zestaw narzędzi do ucieczki łańcucha, * rozważ Apache Commons Lang i jego klasę StringEscapeUtils. * Nie używamy tej klasy tutaj, aby uniknąć zależności środowiska wykonawczego * na Commons Lang tylko do ucieczki HTML. Ponadto funkcja ucieczki HTML * Springa jest bardziej elastyczna i w 100% zgodna z HTML 4.0. Jeśli już używasz wspólnych Apache w swoim projekcie, prawdopodobnie powinieneś użyć StringEscapeUtils z Apache
andreyro
10

Chociaż odpowiedź @dfa org.apache.commons.lang.StringEscapeUtils.escapeHtmljest dobra i korzystałem z niej w przeszłości, nie należy jej używać do zmiany znaczenia atrybutów HTML (lub XML), w przeciwnym razie białe znaki zostaną znormalizowane (co oznacza, że ​​wszystkie sąsiadujące białe znaki stają się pojedynczą spacją).

Wiem o tym, ponieważ zgłoszono błędy w mojej bibliotece (JATL) dotyczące atrybutów, w których nie zostały zachowane białe znaki. Mam więc klasę drop (wklej i wklej) (której część ukradłem z JDOM), która odróżnia ucieczkę atrybutów i zawartości elementu .

Chociaż w przeszłości mogło to nie mieć większego znaczenia (prawidłowe ucieczkowanie atrybutów), staje się coraz bardziej interesujące, biorąc pod uwagę użycie data-atrybutu HTML5 .

Adam Gent
źródło
9

org.apache.commons.lang3.StringEscapeUtils jest teraz przestarzały. Musisz teraz użyć org.apache.commons.text.StringEscapeUtils przez

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-text</artifactId>
        <version>${commons.text.version}</version>
    </dependency>
Luca Stancapiano
źródło
1

Większość bibliotek oferuje unikanie wszystkiego, co się da, w tym setek symboli i tysięcy znaków spoza ASCII, co nie jest tym, czego chcesz w świecie UTF-8.

Ponadto, jak zauważył Jeff Williams, nie ma jednej opcji „escape HTML”, istnieje kilka kontekstów.

Zakładając, że nigdy nie używasz nieocenionych atrybutów i pamiętając, że istnieją różne konteksty, napisałem własną wersję:

private static final long BODY_ESCAPE =
        1L << '&' | 1L << '<' | 1L << '>';
private static final long DOUBLE_QUOTED_ATTR_ESCAPE =
        1L << '"' | 1L << '&' | 1L << '<' | 1L << '>';
private static final long SINGLE_QUOTED_ATTR_ESCAPE =
        1L << '"' | 1L << '&' | 1L << '\'' | 1L << '<' | 1L << '>';

// 'quot' and 'apos' are 1 char longer than '#34' and '#39' which I've decided to use
private static final String REPLACEMENTS = "&#34;&amp;&#39;&lt;&gt;";
private static final int REPL_SLICES = /*  |0,   5,   10,  15, 19, 23*/
        5<<5 | 10<<10 | 15<<15 | 19<<20 | 23<<25;
// These 5-bit numbers packed into a single int
// are indices within REPLACEMENTS which is a 'flat' String[]

private static void appendEscaped(
        StringBuilder builder,
        CharSequence content,
        long escapes // pass BODY_ESCAPE or *_QUOTED_ATTR_ESCAPE here
) {
    int startIdx = 0, len = content.length();
    for (int i = 0; i < len; i++) {
        char c = content.charAt(i);
        long one;
        if (((c & 63) == c) && ((one = 1L << c) & escapes) != 0) {
        // -^^^^^^^^^^^^^^^   -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        // |                  | take only dangerous characters
        // | java shifts longs by 6 least significant bits,
        // | e. g. << 0b110111111 is same as >> 0b111111.
        // | Filter out bigger characters

            int index = Long.bitCount(SINGLE_QUOTED_ATTR_ESCAPE & (one - 1));
            builder.append(content, startIdx, i /* exclusive */)
                    .append(REPLACEMENTS,
                            REPL_SLICES >>> 5*index & 31,
                            REPL_SLICES >>> 5*(index+1) & 31);
            startIdx = i + 1;
        }
    }
    builder.append(content, startIdx, len);
}

Rozważ wklejenie kopii z Gist bez limitu długości linii .

Miha_x64
źródło