Método recomendado para escapar de HTML em Java

262

Existe uma maneira recomendada para escapar <, >, "e &caracteres quando a saída HTML em código Java simples? (Além de fazer o seguinte manualmente).

String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = source.replace("<", "&lt;").replace("&", "&amp;"); // ...
Ben Lings
fonte
2
Esteja ciente de que, se você estiver produzindo um atributo HTML não citado, outros caracteres, como espaço, tabulação, backspace, etc., podem permitir que os invasores introduzam atributos javascript sem nenhum dos caracteres listados. Consulte a Folha de dicas sobre prevenção do OWASP XSS para obter mais informações.
Jeff Williams
BTW, neste código, você deve escapar "&" before "<" para que isso funcione corretamente ("& lt;" seja substituído por "& amp; lt;" caso contrário, que será renderizado como "& lt;" então, não "< "):source.replace("&", "&amp;").replace("<", "&lt;");
Tey '23 de

Respostas:

261

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

Para a versão 3 :

import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;
// ...
String escaped = escapeHtml4(source);
dfa
fonte
2
Embora StringEscapeUtilsseja bom, ele não escapará do espaço em branco corretamente para atributos, se você desejar evitar a normalização do espaço em branco HTML / XML. Veja minha resposta para maiores detalhes.
7263 Adam Gent
21
O exemplo acima está quebrado. Use o método escapeHtml4 () agora.
stackoverflowuser2010
3
Para os fãs da goiaba, veja a resposta da okranz abaixo.
George Hawkins
2
Se a página da Web possui codificação UTF-8, tudo o que precisamos é do htmlEscaper do Guava, que escapa apenas dos cinco caracteres ASCII a seguir: '"& <>. O escapeHtml () do Apache também substitui caracteres não ASCII, incluindo acentos que parecem desnecessários na Web UTF-8 páginas?
zdenekca
4
Agora está obsoleto em commons-lang3. Ele foi transferido para commons.apache.org/proper/commons-text
Danny
137

Uma alternativa ao Apache Commons: use o método SpringHtmlUtils.htmlEscape(String input) .

Adamski
fonte
9
Obrigado. Eu usei (em vez StringEscapeUtils.escapeHtml()do apache-commons2.6) porque deixa os caracteres russos como estão.
Slava Semushin
6
É bom saber disso. TBH, hoje em dia, dou muito para as coisas do Apache.
31712 Adamski
1
Eu também o usei, deixando caracteres chineses como estão também.
precisa saber é o seguinte
Como ele se compara à alternativa da goiaba mencionada abaixo?
vishvAs vAsuki
2
E também codifica o apóstrofo, por isso é realmente útil, ao contrário apache StringEscapeUtils
David Balažic
57

Bom método curto:

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

Com base em https://stackoverflow.com/a/8838023/1199155 (o amplificador está ausente). Os quatro caracteres marcados na cláusula if são os únicos abaixo de 128, de acordo com http://www.w3.org/TR/html4/sgml/entities.html

Bruno Eberhard
fonte
Agradável. Ele não usa as "versões html" das codificações (exemplo: "á" seria "& aacute;" em vez de "& # 225;"), mas como as numéricas funcionam mesmo no IE7, acho que não precisa se preocupar. Obrigado.
Nonzaprej
Por que você codifica todos esses caracteres quando o OP pede para escapar dos 4 caracteres relevantes? Você está desperdiçando CPU e memória.
David Balažic
1
Você esqueceu o apóstrofo. Assim, as pessoas podem injetar atributos não citados em todos os lugares em que esse código é usado para escapar dos valores dos atributos.
David Balažic
45

Existe uma versão mais recente da biblioteca Apache Commons Lang e ela usa um nome de pacote diferente (org.apache.commons.lang3). O StringEscapeUtilsagora tem métodos estáticos diferentes para escapar de diferentes tipos de documentos ( http://commons.apache.org/proper/commons-lang/javadocs/api-3.0/index.html ). Então, para escapar da string HTML versão 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");
Martin Dimitrov
fonte
3
Infelizmente não existe nada para HTML 5, nem os documentos do Apache especificar se é adequada para usar escapeHtml4 para HTML 5.
Paul Vincent Craven
43

Para quem usa o 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);
okrasz
fonte
40

No android (API 16 ou superior), você pode:

Html.escapeHtml(textToScape);

ou para API mais baixa:

TextUtils.htmlEncode(textToScape);
OriolJ
fonte
Existe algum motivo para usar em escapeHtmlvez de htmlEncode?
Muz
2
Veja também minha pergunta sobre a diferença entre esses dois. (@Muz)
JonasCz - Restabelece Monica
37

Tenha cuidado com isso. Existem vários 'contextos' diferentes em um documento HTML: dentro de um elemento, valor de atributo entre aspas, valor de atributo não entre aspas, atributo de URL, javascript, CSS, etc. Você precisará usar um método de codificação diferente para cada um dos para impedir a criação de scripts entre sites (XSS). Consulte a Folha de dicas de prevenção do OWASP XSS para obter detalhes sobre cada um desses contextos. Você pode encontrar métodos de escape para cada um desses contextos na biblioteca OWASP ESAPI - https://github.com/ESAPI/esapi-java-legacy .

Jeff Williams
fonte
6
Obrigado por apontar que o contexto em que você deseja codificar a saída é muito importante. O termo "codificar" também é um verbo muito mais apropriado do que "escapar" também. Fuga implica algum tipo de corte especial, ao contrário de "como faço para codificar esta corda para:? Um atributo XHTML / SQL consulta parâmetro / seqüência de impressão PostScript / campo de saída CSV
Roboprog
5
'Codificar' e 'escapar' são amplamente utilizados para descrever isso. O termo "escape" é geralmente usado quando o processo é adicionar um "caractere de escape" antes de um caractere sintaticamente relevante, como escapar de um caractere de aspas com uma barra invertida \ "O termo" codificar "é mais usado normalmente quando você traduz um caracteres em uma forma diferente, como URL codificar o caracter de aspas% 22 ou codificação entidade HTML como & # x22 ou @quot.
Jeff Williams
1
Para poupar algum googling, olhar para a classe Encoder static.javadoc.io/org.owasp.esapi/esapi/2.0.1/org/owasp/esapi/...
Jakub Bochenski
14

Para alguns propósitos, HtmlUtils :

import org.springframework.web.util.HtmlUtils;
[...]
HtmlUtils.htmlEscapeDecimal("&"); //gives &#38;
HtmlUtils.htmlEscape("&"); //gives &amp;
AUU
fonte
1
A partir dos comentários HtmlUtils da primavera: * <p> Para um conjunto abrangente de utilitários de escape de String, * considere o Apache Commons Lang e sua classe StringEscapeUtils. * Não estamos usando essa classe aqui para evitar uma dependência de tempo de execução * no Commons Lang apenas para escape de HTML. Além disso, o escape HTML do Spring * é mais flexível e 100% compatível com HTML 4.0. Se você já estiver usando commons Apache em seu projeto provavelmente você deve usar os StringEscapeUtils de apache
andreyro
10

Embora a resposta do @dfa org.apache.commons.lang.StringEscapeUtils.escapeHtmlseja boa e eu a tenha usado no passado, ela não deve ser usada para escapar dos atributos HTML (ou XML), caso contrário, o espaço em branco será normalizado (o que significa que todos os caracteres de espaço em branco adjacentes se tornarão um espaço único).

Eu sei disso porque tive bugs arquivados na minha biblioteca (JATL) para atributos em que o espaço em branco não foi preservado. Portanto, tenho uma queda na classe (copiar e colar) (da qual roubei algumas do JDOM) que diferencia o escape de atributos e conteúdo de elementos .

Embora isso possa não ter importado tanto no passado (escape adequado de atributos), ele se torna cada vez mais interessante, devido ao uso do data-atributo HTML5 .

Adam Gent
fonte
9

org.apache.commons.lang3.StringEscapeUtils agora está obsoleto. Agora você deve usar org.apache.commons.text.StringEscapeUtils by

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

A maioria das bibliotecas oferece escapar de tudo o que pode, incluindo centenas de símbolos e milhares de caracteres não ASCII, o que não é o que você deseja no mundo UTF-8.

Além disso, como Jeff Williams observou, não há uma opção única de "escape HTML", existem vários contextos.

Supondo que você nunca use atributos não citados, e tendo em mente que existem diferentes contextos, ele escreveu minha própria versão:

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

Considere copiar e colar do Gist sem limite de comprimento de linha .

Miha_x64
fonte