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;"); // ...

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);

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.


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


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");

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);

40

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

Html.escapeHtml(textToScape);

lub dla niższego API:

TextUtils.htmlEncode(textToScape);

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 .


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;

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 .


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>

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 .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.