Рекомендуемый метод для экранирования HTML в Java
Есть ли рекомендуемый способ побега <
, >
, "
а также &
символы при выводе HTML в простом коде Java? (Кроме ручного выполнения следующего, то есть).
String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = source.replace("<", "<").replace("&", "&"); // ...
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 &
HtmlUtils.htmlEscape("&")` //gives &
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 = ""&'<>";
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 без ограничения длины строки.