Рекомендуемый метод для экранирования HTML в Java

262

Есть ли способ рекомендуется бежать <, >, "и &символы при выводе HTML в обычном коде Java? (Кроме ручного выполнения следующего, то есть).

String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = source.replace("<", "&lt;").replace("&", "&amp;"); // ...
Бен Лингс
источник
2
Имейте в виду, что если вы выводите в атрибут HTML без кавычек, другие символы, такие как пробел, табуляция, клавиша возврата и т. Д., Могут позволить злоумышленникам ввести атрибуты javascript без перечисленных символов. Смотрите OWASP XSS Предотвращение Шпаргалку для получения дополнительной информации.
Джефф Уильямс
Кстати, в этом коде вы должны экранировать «&» перед «<», чтобы это работало должным образом («& lt;» заменяется на «& amp; lt;» в противном случае, которое отображается как «& lt;» тогда, а не «< "):source.replace("&", "&amp;").replace("<", "&lt;");
Tey '23

Ответы:

261

StringEscapeUtils от 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);

Для версии 3 :

import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;
// ...
String escaped = escapeHtml4(source);
DFA
источник
2
Хотя StringEscapeUtilsэто и хорошо, он не будет корректно выходить из пробелов для атрибутов, если вы хотите избежать нормализации пробелов в HTML / XML. Смотрите мой ответ для более подробной информации.
Адам Гент
21
Приведенный выше пример не работает. Используйте метод escapeHtml4 () сейчас.
stackoverflowuser2010
3
Для поклонников гуавы см . Ответ Окранца ниже.
Джордж Хокинс
2
Если веб-страница имеет кодировку UTF-8, то все, что нам нужно, это htmlEscaper Гуавы, который экранирует только следующие пять символов ASCII: '"& <>. EscapeHtml () Apache также заменяет символы не-ASCII, включая акценты, которые, как представляется, не нужны в сети UTF-8. страницы?
zdenekca
4
Сейчас не рекомендуется использовать commons-lang3. Это было перемещено в commons.apache.org/proper/commons-text
Дэнни
137

Альтернатива Apache Commons: Использование Spring «s HtmlUtils.htmlEscape(String input)метод.

Адамский
источник
9
Спасибо. Я использовал его (вместо StringEscapeUtils.escapeHtml()от apache-commons2.6) , поскольку она оставляет русские символы как есть.
Слава Семушин
6
Это хорошо знать. ТБХ В эти дни я делаю вещи для Apache.
Адамски
1
Я тоже его использовал, он оставляет иероглифы как есть.
SmartWJW
Как это сравнивается с альтернативой гуавы, упомянутой ниже?
vishvAs vAsuki
2
И он также кодирует апостроф, поэтому он действительно полезен, в отличие от apache StringEscapeUtils
David Balažic
58

Хороший короткий метод:

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

Основано на https://stackoverflow.com/a/8838023/1199155 (там отсутствует усилитель). Согласно условию http://www.w3.org/TR/html4/sgml/entities.html, четыре символа, отмеченные в предложении if, являются единственными символами ниже 128.

Бруно Эберхард
источник
Ницца. Он не использует «html-версии» кодировок (пример: «á» будет «& aacute;» вместо «& # 225;»), но поскольку числовые работают даже в IE7, я полагаю, что нет надо волноваться Спасибо.
nonzaprej
Почему вы кодируете все эти символы, когда ОП просит экранировать 4 соответствующих символа? Вы тратите процессор и память.
Давид Балажич,
1
Вы забыли апостроф. Таким образом, люди могут вводить атрибуты без кавычек везде, где этот код используется для экранирования значений атрибутов.
Давид Балажич,
45

Существует более новая версия библиотеки Apache Commons Lang, в которой используется другое имя пакета (org.apache.commons.lang3). В StringEscapeUtilsнастоящее время есть разные статические методы для экранирования документов разных типов ( http://commons.apache.org/proper/commons-lang/javadocs/api-3.0/index.html ). Итак, чтобы избежать строки HTML версии 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
К сожалению, для HTML 5 ничего не существует, и в документах Apache не указано, правильно ли использовать escapeHtml4 для HTML 5.
Пол Винсент Крейвен,
43

Для тех, кто использует 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
источник
40

На Android (API 16 или выше) вы можете:

Html.escapeHtml(textToScape);

или для более низкого API:

TextUtils.htmlEncode(textToScape);
OriolJ
источник
Есть ли причина использовать escapeHtmlвместо htmlEncode?
Муз
2
Смотрите также мой мой вопрос о разнице между этими двумя. (@Muz)
JonasCz - Восстановить Монику
37

Будьте осторожны с этим. В HTML-документе есть несколько различных «контекстов»: внутри элемента, значения атрибута в кавычках, значения атрибута без кавычек, атрибута URL, javascript, CSS и т. Д. Вам нужно будет использовать разные методы кодирования для каждого из они предотвращают межсайтовый скриптинг (XSS). Проверка на листе OWASP XSS Предупреждение Чит для получения подробной информации о каждом из этих контекстов. Вы можете найти экранирующие методы для каждого из этих контекстов в библиотеке OWASP ESAPI - https://github.com/ESAPI/esapi-java-legacy .

Джефф Уильямс
источник
6
СПАСИБО за указание на то, что контекст, в котором вы хотите закодировать вывод, очень важен. Термин «кодировать» также является гораздо более подходящим глаголом, чем «escape». Escape подразумевает какой-то особый взлом, а не «как мне закодировать эту строку для: атрибута XHTML / параметра запроса SQL / строки печати PostScript / поля вывода CSV?»
Roboprog
5
'Encode' и 'escape' широко используются для описания этого. Термин «escape» обычно используется, когда процесс должен добавить «escape-символ» перед синтаксически значимым символом, таким как экранирование символа кавычки с обратной косой чертой. «Термин« закодировать »чаще используется при переводе символ в другую форму, например, URL-код, кодирующий символ кавычки% 22, или HTML-объект, кодирующий как & # x22 или @quot.
Джефф Уильямс,
owasp-esapi-java.googlecode.com/svn/trunk_doc/latest/index.html . ссылка в настоящее время сломал
AndreW паштет
1
Чтобы сэкономить на поиске, поищите
Якуб Боченски,
14

Для некоторых целей HtmlUtils :

import org.springframework.web.util.HtmlUtils;
[...]
HtmlUtils.htmlEscapeDecimal("&"); //gives &#38;
HtmlUtils.htmlEscape("&"); //gives &amp;
AUU
источник
1
Из весенних комментариев HtmlUtils: * <p> Для полного набора утилит для выхода из String * рассмотрите Apache Commons Lang и его класс StringEscapeUtils. * Мы не используем этот класс здесь, чтобы избежать зависимости во время выполнения * от Commons Lang только для экранирования HTML. Более того, * HTML-переход от Spring более гибок и на 100% совместим с HTML 4.0. Если вы уже используете Apache в вашем проекте, возможно, вам следует использовать StringEscapeUtils из apache
andreyro
10

Хотя ответ @dfa org.apache.commons.lang.StringEscapeUtils.escapeHtmlхорош, и я использовал его в прошлом, его не следует использовать для экранирования атрибутов HTML (или XML), в противном случае пробел будет нормализован (то есть все соседние пробельные символы станут одним пробелом).

Я знаю это, потому что в моей библиотеке (JATL) были обнаружены ошибки, касающиеся атрибутов, в которых пробелы не сохранялись. Таким образом, у меня есть класс drop (copy n 'paste) (часть которого я украл из JDOM), который различает экранирование атрибутов и содержимого элементов .

Хотя это, возможно, и не имело большого значения в прошлом (правильное экранирование атрибутов), оно становится все более интересным, учитывая использование data-атрибутов HTML5 .

Адам Гент
источник
9

org.apache.commons.lang3.StringEscapeUtils больше не поддерживается. Теперь вы должны использовать org.apache.commons.text.StringEscapeUtils

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-text</artifactId>
        <version>${commons.text.version}</version>
    </dependency>
Люка Станкапиано
источник
1

Большинство библиотек предлагают экранирование всего, что могут, в том числе сотни символов и тысячи не-ASCII символов, а это не то, что вам нужно в мире UTF-8.

Кроме того, как отметил Джефф Уильямс, нет единой опции «escape HTML», есть несколько контекстов.

Предполагая, что вы никогда не используете атрибуты без кавычек, и учитывая, что существуют различные контексты, он написал мою собственную версию:

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

Рассмотрите возможность копирования из Gist без ограничения длины строки .

Miha_x64
источник