Método recomendado para escapar de HTML en Java


262

¿Hay un método recomendado para escapar <, >, "y &caracteres cuando se da salida HTML en código Java normal? (Aparte de hacer manualmente lo siguiente, es decir).

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

2
Tenga en cuenta que si está generando un atributo HTML sin comillas, otros caracteres como el espacio, la pestaña, el espacio de retroceso, etc. pueden permitir a los atacantes introducir atributos de JavaScript sin ninguno de los caracteres enumerados. Consulte la hoja de trucos de prevención OWASP XSS para obtener más información.
Jeff Williams

Por cierto, en este código, debe escapar "&" antes de "<" para que esto funcione correctamente ("& lt;" ser reemplazado por "& amp; lt;" de lo contrario, se representa como "& lt;" entonces, no "< "):source.replace("&", "&amp;").replace("<", "&lt;");
Tey '23 de

Respuestas:


261

StringEscapeUtils de 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 la versión 3 :

import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;
// ...
String escaped = escapeHtml4(source);

2
Si bien StringEscapeUtilses bueno, no escapará de los espacios en blanco correctamente para los atributos si desea evitar la normalización de espacios en blanco HTML / XML. Vea mi respuesta para más detalles.
Adam Gent

21
El ejemplo anterior está roto. Utilice el método escapeHtml4 () ahora.
stackoverflowuser2010

3
Para los fanáticos de la guayaba, vea la respuesta de okranz a continuación.
George Hawkins el

2
Si la página web tiene codificación UTF-8, todo lo que necesitamos es el htmlEscaper de Guava que escapa solo a los siguientes cinco caracteres ASCII: '"& <>. El escapeHtml () de Apache también reemplaza los caracteres no ASCII, incluidos los acentos que parecen innecesarios con la web UTF-8 páginas?
zdenekca

44
Ahora está en desuso en commons-lang3. Fue trasladado a commons.apache.org/proper/commons-text
Danny

137

Una alternativa a Apache Commons: uso el HtmlUtils.htmlEscape(String input)método de Spring .


9
Gracias. Lo he utilizado (en lugar de StringEscapeUtils.escapeHtml()a partir de apache-commons2.6), ya que deja caracteres rusos como está.
Slava Semushin

66
Es bueno saberlo. TBH Le doy a las cosas de Apache un amplio espacio en estos días.
Adamski

1
También lo he usado, deja los caracteres chinos como están también.
smartwjw

¿Cómo se compara con la alternativa de guayaba mencionada a continuación?
vishvAs vAsuki

2
Y también codifica el apóstrofe, por lo que es realmente útil, a diferencia de Apache StringEscapeUtils
David Balažic

58

Buen método corto:

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

Basado en https://stackoverflow.com/a/8838023/1199155 (falta el amplificador allí). Los cuatro caracteres marcados en la cláusula if son los únicos inferiores a 128, de acuerdo con http://www.w3.org/TR/html4/sgml/entities.html


Agradable. No utiliza las "versiones html" de las codificaciones (ejemplo: "á" sería "& aacute;" en lugar de "& # 225;"), pero dado que las numéricas funcionan incluso en IE7, supongo que no Tienes que preocuparte. Gracias.
nonzaprej

¿Por qué codifica todos esos caracteres cuando el OP le pidió escapar de los 4 caracteres relevantes? Estás desperdiciando CPU y memoria.
David Balažic

1
Olvidaste el apóstrofe. Por lo tanto, las personas pueden inyectar atributos sin comillas en cualquier lugar donde se use este código para escapar de los valores de los atributos.
David Balažic

45

Hay una versión más reciente de la biblioteca Lang de Apache Commons y utiliza un nombre de paquete diferente (org.apache.commons.lang3). El StringEscapeUtilsahora tiene diferentes métodos estáticos para escapar de diferentes tipos de documentos ( http://commons.apache.org/proper/commons-lang/javadocs/api-3.0/index.html ). Entonces, para escapar de la cadena HTML versión 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
Lamentablemente, no existe nada para HTML 5, ni los documentos de Apache especifican si es apropiado usar escapeHtml4 para HTML 5.
Paul Vincent Craven

43

Para aquellos que usan 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

En Android (API 16 o superior) puedes:

Html.escapeHtml(textToScape);

o para API inferior:

TextUtils.htmlEncode(textToScape);

¿Hay alguna razón para usar en escapeHtmllugar de htmlEncode?
Muz

2
Vea también mi pregunta sobre la diferencia entre estos dos. (@Muz)
JonasCz - Restablece a Monica

37

Ten cuidado con esto. Hay un número de 'contextos' diferentes dentro de un documento HTML: dentro de un elemento, valor de atributo entre comillas, valor de atributo sin comillas, atributo URL, javascript, CSS, etc. Deberá usar un método de codificación diferente para cada uno de ellos. estos para evitar secuencias de comandos entre sitios (XSS). Consulte la hoja de trucos de prevención OWASP XSS para obtener detalles sobre cada uno de estos contextos. Puede encontrar métodos de escape para cada uno de estos contextos en la biblioteca OWASP ESAPI: https://github.com/ESAPI/esapi-java-legacy .


66
GRACIAS por señalar que el contexto en el que desea codificar la salida es muy importante. El término "codificar" también es un verbo mucho más apropiado que "escapar". Escape implica algún tipo de pirateo especial, en lugar de "¿cómo codifico esta cadena para: un atributo XHTML / parámetro de consulta SQL / cadena de impresión PostScript / campo de salida CSV?
Roboprog

55
'Codificar' y 'escapar' se usan ampliamente para describir esto. El término "escape" se usa generalmente cuando el proceso consiste en agregar un "carácter de escape" antes de un carácter sintácticamente relevante, como escapar de un carácter de comillas con una barra invertida \ "El término" codificar "se usa más típicamente cuando traduce un carácter en una forma diferente, como URL que codifica el carácter de comilla% 22 o entidad de HTML que codifica como & # x22 o @quot.
Jeff Williams


1
Para ahorrar un poco de google, busque la clase Encoder static.javadoc.io/org.owasp.esapi/esapi/2.0.1/org/owasp/esapi/…
Jakub Bochenski

14

Para algunos propósitos, HtmlUtils :

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

1
Desde la primavera, HtmlUtils comenta: * <p> Para un conjunto completo de utilidades de escape String, * considere Apache Commons Lang y su clase StringEscapeUtils. * No estamos usando esa clase aquí para evitar una dependencia del tiempo de ejecución * en Commons Lang solo para el escape de HTML. Además, el escape HTML de Spring * es más flexible y 100% compatible con HTML 4.0. Si ya está usando Apache commons en su proyecto, probablemente debería usar StringEscapeUtils de apache
andreyro

10

Si bien la respuesta @dfa de org.apache.commons.lang.StringEscapeUtils.escapeHtmles agradable y la he usado en el pasado, no debería usarse para escapar de los atributos HTML (o XML) contrario, el espacio en blanco se normalizará (lo que significa que todos los caracteres de espacio en blanco adyacentes se convierten en un solo espacio).

Sé esto porque he tenido errores archivados en mi biblioteca (JATL) para los atributos donde no se conservó el espacio en blanco. Por lo tanto, tengo una clase de caída (copiar y pegar) (de la que robé algunos de JDOM) que diferencia el escape de atributos y contenido de elementos .

Si bien esto puede no haber importado tanto en el pasado (escape de atributo adecuado), cada vez tiene más interés dado el uso del uso de data-atributos de HTML5 .


9

org.apache.commons.lang3.StringEscapeUtils ahora está en desuso. Ahora debe usar org.apache.commons.text.StringEscapeUtils por

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

1

La mayoría de las bibliotecas ofrecen escapar de todo lo que pueden, incluidos cientos de símbolos y miles de caracteres que no son ASCII, que no es lo que quieres en el mundo UTF-8.

Además, como señaló Jeff Williams, no existe una única opción de "escape HTML", hay varios contextos.

Suponiendo que nunca use atributos sin comillas, y teniendo en cuenta que existen diferentes contextos, he escrito mi propia versión:

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 y pegar desde Gist sin límite de longitud de línea .

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.