在Java中转义HTML的推荐方法


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'

Answers:


261

来自Apache Commons Lang的StringEscapeUtils

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

2
虽然StringEscapeUtils是很好,如果你想避免HTML / XML空白规范化也不会逃脱空白正确的属性。请参阅我的答案以获取更多详细信息。
亚当·根特

21
上面的例子坏了。立即使用escapeHtml4()方法。
stackoverflowuser2010

3
对于番石榴粉丝,请参阅下面的okranz答案
乔治·霍金斯

2
如果网页具有UTF-8编码,那么我们只需要Guava的htmlEscaper即可,它仅转义以下五个ASCII字符:““&<>。Apache的escapeHtml()还将替换非ASCII字符,包括带有重音符号的字符,而UTF-8 web似乎不需要页?
zdenekca 2015年

4
现在在commons-lang3中已弃用。它已移至 commons.apache.org/proper/commons-text
Danny,

137

Apache Commons的替代方法:使用SpringHtmlUtils.htmlEscape(String input)方法。


9
谢谢。我(而不是用它StringEscapeUtils.escapeHtml()apache-commons2.6),因为它留下俄语字符不变。
Slava Semushin 2012年

6
很高兴知道。TBH这些天,我给了Apache大量的东西。
亚当斯基2012年

1
我也用过它,它也照原样保留汉字。
smartwjw

与下面提到的番石榴替代品相比如何?
vishvAs vAsuki

2
而且它也编码撇号,所以它实际上是有用的,不像阿帕奇StringEscapeUtils
大卫Balažic

57

不错的简短方法:

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

当OP要求转义4个相关字符时,为什么还要编码所有这些字符?您正在浪费CPU和内存。
DavidBalažic18年

1
您忘记了撇号。因此,人们可以在使用此代码转义属性值的任何地方注入未加引号的属性。
DavidBalažic18年

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文档也没有指定对于HTML 5使用escapeHtml4是否合适。–
Paul Vincent Craven

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

40

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

Html.escapeHtml(textToScape);

或针对较低的API:

TextUtils.htmlEncode(textToScape);

有什么理由escapeHtml代替使用htmlEncode吗?
Muz

2
另请参阅我关于这两者之间的区别的我的问题。(@Muz)
JonasCz-恢复莫妮卡

37

请注意这一点。HTML文档中有许多不同的“上下文”:在元素内,带引号的属性值,未带引号的属性值,URL属性,javascript,CSS等...对于每种情况,您都需要使用不同的编码方法这些可以防止跨站点脚本(XSS)。查看OWASP XSS预防备忘单,以获取有关每种情况的详细信息。您可以在OWASP ESAPI库(https://github.com/ESAPI/esapi-java-legacy)中找到每种上下文的转义方法。


6
感谢您指出,您希望对输出进行编码的上下文非常重要。同样,术语“编码”也是比“转义”更合适的动词。Escape意味着某种特殊的破解,与“我如何为以下字符串编码:XHTML属性/ SQL查询参数/ PostScript打印字符串/ CSV输出字段?”
相反

5
“编码”和“转义”都广泛用于描述这一点。术语“转义”通常在处理过程中要在语法相关的字符之前添加“转义字符”时使用,例如用反斜杠将引号字符转义。字符转换为其他形式,例如URL编码引号字符%22或HTML实体编码为&#x22或@quot。–
Jeff Williams


1
为了节省您的google搜索,寻找编码器类static.javadoc.io/org.owasp.esapi/esapi/2.0.1/org/owasp/esapi/...
的Jakub博琴斯基

14

出于某些目的,HtmlUtils

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

1
从春季的HtmlUtils注释中:* <p>对于一组全面的字符串转义实用程序,请考虑Apache Commons Lang及其StringEscapeUtils类。*我们这里不使用该类来避免对Commons Lang的运行时依赖*仅用于HTML转义。此外,Spring的* HTML转义更灵活,并且100%兼容HTML 4.0。如果您已经在项目中使用Apache Commons,则应该使用apache的StringEscapeUtils
andreyro 19/09/13

10

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

我知道这一点是因为我在我的库(JATL)上有一些针对未保留空格的属性的错误。因此,我有一(复制n'paste)类(我从JDOM中偷了一些)来区别转义属性和元素内容

尽管过去可能没有多大作用(适当的属性转义),但是鉴于使用HTML5 data-属性使用,它变得越来越引起人们的关注。


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世界中不是您想要的。

而且,正如Jeff Williams指出的那样,没有一个单独的“转义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复制粘贴而没有行长限制

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.