在纯Java中转义HTML符号的推荐方式是什么?
在纯Java代码中输出HTML时,有没有一种推荐的方法来转义<
、>
、"
和&
字符?(除了手动做以下工作外,还有其他方法。)
String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = source.replace("<", "<").replace("&", "&"); // ...
source.replace("&", "&").replace("<", "<");
- Tey' 2020-02-23
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);
StringEscapeUtils
很好,但如果你想避免HTML/XML的空白规范化,它就不能为属性正确转义。请看我的回答,以了解更多细节。
- Adam Gent 2013-08-07
一个替代Apache Commons的方法。使用Spring的HtmlUtils.htmlEscape(String input)
方法。
apache-commons
2.6中的StringEscapeUtils.escapeHtml()
),因为它让俄罗斯字符保持原样。
- Slava Semushin 2012-07-30
很好的简短方法。
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的字符。
有一个更新的Apache Commons Lang库版本,它使用不同的包名(org.apache.commons.lang3)。现在StringEscapeUtils
有不同的静态方法来转义不同类型的文档(http://commons.apache.org/proper/commons-lang/javadocs/api-3.0/index.html)。所以要转义HTML4.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预防骗局表,了解这些情况的细节。你可以在OWASP ESAPI库中找到这些语境的转义方法--https://github.com/ESAPI/esapi-java-legacy。
在安卓系统(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)曾因属性没有保留空格而被提出过错误。因此,我有一个滴入(复制粘贴)的类(我从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
返回String中的char值的IntStream
。然后我们可以使用mapToObj
来转义字符代码大于127的字符(非ASCII字符)以及双引号("
)、单引号('
)、左角括号(<
)、右角括号(>
)和安培号(&
)。Collectors.joining
将String
串联起来。
为了更好地处理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());
}
大多数库都提供了转义功能,包括数百个符号和数千个非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 = ""&'<";
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);
}
}
考虑从Gist中复制粘贴,不受行长限制。
UPD。正如另一个答案所建议的,>
转义是没有必要的;另外,"
在attr='…'
内也是允许的。我已经相应地更新了代码。
你可以自己检查一下。
<!DOCTYPE html>
<html lang="en">
<head><title>Test</title></head>
<body>
<p title="<"I'm double-quoted!">"><"Hello!"></p>
<p title='<"I'm single-quoted!">'><"Goodbye!"></p>
</body>
</html>