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

Есть ли рекомендуемый способ побега <, >, " а также & символы при выводе 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;"); // ...

12 ответов

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

Альтернатива Apache Commons: используйте SpringHtmlUtils.htmlEscape(String input) метод.

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

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 == '&') {
            out.append("&#");
            out.append((int) c);
            out.append(';');
        } else {
            out.append(c);
        }
    }
    return out.toString();
}

Основано на /questions/2988705/suschestvuet-li-klass-jdk-dlya-kodirovaniya-html-no-ne-kodirovaniya-url/2988719#2988719 (там отсутствует усилитель). Согласно условию http://www.w3.org/TR/html4/sgml/entities.html четыре символа, отмеченные в предложении if, являются единственными символами ниже 128.

Существует более новая версия библиотеки 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");

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

Будьте осторожны с этим. В HTML-документе есть несколько разных "контекстов": внутри элемента, значения атрибута в кавычках, значения атрибута без кавычек, атрибута URL, javascript, CSS и т. Д. Вам нужно будет использовать разные методы кодирования для каждого из они предотвращают межсайтовый скриптинг (XSS). Для получения подробной информации о каждом из этих контекстов ознакомьтесь с Шпаргалкой по профилактике OWASP XSS - https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet. Вы можете найти экранирующие методы для каждого из этих контекстов в библиотеке OWASP ESAPI - https://github.com/ESAPI/esapi-java-legacy.

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

Html.escapeHtml(textToScape);

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

TextUtils.htmlEncode(textToScape);

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

import org.springframework.web.util.HtmlUtils;
[...]
HtmlUtils.htmlEscapeDecimal("&")` //gives &#38;
HtmlUtils.htmlEscape("&")` //gives &amp;

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>

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

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

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

Решение Java 8+:

      public static String escapeHTML(String str) {
    return str.chars().mapToObj(c -> c > 127 || "\"'<>&".indexOf(c) != -1 ?
       "&#" + c + ";" : String.valueOf((char) c)).collect(Collectors.joining());
}

String#chars возвращает IntStreamзначений char из String. Затем мы можем использовать mapToObj для экранирования символов с кодом символа больше 127 (символы, отличные от ASCII), а также двойной кавычки ( "), одинарная кавычка ( '), левая угловая скобка ( <), правая угловая скобка ( >) и амперсанд ( &). Collectors.joining объединяет Stringснова вместе.

Чтобы лучше обрабатывать символы Юникода, String#codePoints можно использовать вместо этого.

      public static String escapeHTML(String str) {
    return str.codePoints().mapToObj(c -> c > 127 || "\"'<>&".indexOf(c) != -1 ?
            "&#" + c + ";" : new String(Character.toChars(c)))
       .collect(Collectors.joining());
}

Большинство библиотек предлагают экранирование всего, что они могут, включая сотни символов и тысячи символов, отличных от 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 без ограничения длины строки.

Другие вопросы по тегам