在纯Java中转义HTML符号的推荐方法是什么?

在纯Java代码中输出HTML时,是否有一种推荐的方法来转义<>"&字符?(除了手动执行以下操作之外)。

String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = source.replace("<", "&lt;").replace("&", "&amp;"); // ...
415020 次浏览

StringEscapeUtils from 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的替代方案:使用春天HtmlUtils.htmlEscape(String input)方法。

出于某些目的,HtmlUtils:

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

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

小心这个。在HTML文档中有许多不同的“上下文”:在元素内部,带引号的属性值,不带引号的属性值,URL属性,javascript, CSS等……您需要使用不同的编码方法来防止跨站点脚本(XSS)。检查OWASP跨站防御备忘单以获得这些上下文的详细信息。你可以在OWASP ESAPI库——https://github.com/ESAPI/esapi-java-legacy中找到这些上下文的转义方法。

在android (API 16或更高版本)上,您可以:

Html.escapeHtml(textToScape);

或低空气污染指数:

TextUtils.htmlEncode(textToScape);

虽然org.apache.commons.lang.StringEscapeUtils.escapeHtml的@dfa答案很好,我过去使用过它,但它不应该用于转义HTML(或XML) 属性,否则空白将被规范化(意味着所有相邻的空白字符变成一个空格)。

我知道这一点,因为我的库(JATL)中有一些没有保留空白的属性的bug。因此,我在(复制粘贴)类(我从JDOM中窃取了一些),用于区分属性转义和元素内容中有一个drop。

虽然这在过去可能没有那么重要(适当的属性转义),但考虑到HTML5的data-属性的使用,它越来越有意义。

简单的方法:

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的字符

对于使用谷歌番石榴的人:

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

stringescapeutils现在已弃用。您现在必须使用org.apache.commons.text.StringEscapeUtils by

    <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons.text.version}</version>
</dependency>

大多数库都提供转义,包括数百个符号和数千个非ascii字符,这在UTF-8世界中不是你想要的。

而且,正如Jeff Williams所指出的,没有单一的“转义HTML”选项,有几个上下文。

假设你从未使用过不带引号的属性,并记住存在不同的上下文,它写了我自己的版本:

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


// '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;";
private static final int REPL_SLICES = /*  [0,   5,   10,  15, 19) */
5<<5 | 10<<10 | 15<<15 | 19<<20;
// These 5-bit numbers packed into a single int
// are indices within REPLACEMENTS which is a 'flat' String[]


private static void appendEscaped(
Appendable builder, CharSequence content, long escapes) {
try {
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(ESCAPES & (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);
} catch (IOException e) {
// typically, our Appendable is StringBuilder which does not throw;
// also, there's no way to declare 'if A#append() throws E,
// then appendEscaped() throws E, too'
throw new UncheckedIOException(e);
}
}

考虑从主旨无行长限制复制粘贴。

乌利希期刊指南:正如另一个答案所示,>转义是不必要的;同样,attr='…'中的"也是允许的。我已经相应地更新了代码。

你可以自己看看吧:

<!DOCTYPE html>
<html lang="en">
<head><title>Test</title></head>
<body>


<p title="&lt;&#34;I'm double-quoted!&#34;>">&lt;"Hello!"></p>
<p title='&lt;"I&#39;m single-quoted!">'>&lt;"Goodbye!"></p>


</body>
</html>

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

IntStream0返回String中的char值的IntStream。然后,我们可以使用mapToObj来转义字符代码大于127的字符(非ascii字符)以及双引号(")、单引号(')、左尖括号(<)、右尖括号(>)和&号(&)。Collectors.joining将__abc9重新连接在一起。

为了更好地处理Unicode字符,可以使用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());
}