¿El método más rápido para escapar de las etiquetas HTML como entidades HTML?

98

Estoy escribiendo una extensión de Chrome que implica hacer una gran cantidad de trabajo el siguiente: desinfección cadenas que podrían contener etiquetas HTML, mediante la conversión <, >y &a &lt;, &gt;y &amp;, respectivamente.

(En otras palabras, lo mismo que PHP, htmlspecialchars(str, ENT_NOQUOTES)no creo que haya una necesidad real de convertir caracteres de comillas dobles).

Esta es la función más rápida que he encontrado hasta ahora:

function safe_tags(str) {
    return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') ;
}

Pero todavía hay un gran retraso cuando tengo que ejecutar algunos miles de cadenas de una vez.

¿Alguien puede mejorar esto? Es principalmente para cadenas de entre 10 y 150 caracteres, si eso marca la diferencia.

(Una idea que tuve fue no molestarme en codificar el signo mayor que, ¿habría algún peligro real con eso?)

callum
fuente
2
¿Por qué? En la mayoría de los casos en los que desea hacer esto, desea insertar los datos en el DOM, en cuyo caso debe olvidarse de escapar y simplemente hacer un textNode a partir de él.
Quentin
1
@David Dorward: tal vez quería desinfectar los datos POST y el servidor no redirige los datos correctamente.
Lie Ryan
4
@Lie - si es así, entonces la solución es "Por el amor de Pete, arregla el servidor porque tienes un gran agujero XSS"
Quentin
2
@David Dorward: es posible que el caso sea que él no tenga control sobre el servidor. Recientemente he estado en tal situación en la que estaba escribiendo un script de greasemonkey para solucionar un par de cosas que no me gustan en el sitio web de mi universidad; Tuve que hacer un POST en un servidor sobre el que no tengo control y desinfectar los datos POST usando javascript (dado que los datos sin procesar provienen de un cuadro de texto enriquecido, y por lo tanto tiene un montón de etiquetas html que no funcionan de ida y vuelta en el servidor) . El administrador web ignoraba mi solicitud de que arreglaran el sitio web, así que no tenía otra opción.
Lie Ryan
1
Tengo un caso de uso en el que necesito mostrar un mensaje de error en un div. El mensaje de error puede contener HTML y nuevas líneas. Quiero escapar del HTML y reemplazar las nuevas líneas con <br>. Luego ponga el resultado en un div para mostrarlo.
mozey

Respuestas:

83

Puede intentar pasar una función de devolución de llamada para realizar el reemplazo:

var tagsToReplace = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;'
};

function replaceTag(tag) {
    return tagsToReplace[tag] || tag;
}

function safe_tags_replace(str) {
    return str.replace(/[&<>]/g, replaceTag);
}

Aquí hay una prueba de rendimiento: http://jsperf.com/encode-html-entities para comparar con llamar a la replacefunción repetidamente y usar el método DOM propuesto por Dmitrij.

Tu camino parece ser más rápido ...

Sin embargo, ¿por qué lo necesitas?

Martijn
fuente
2
No hay necesidad de escapar >.
6
En realidad, si coloca el valor de escape en el atributo de un elemento html, debe escapar del símbolo>. De lo contrario, rompería la etiqueta de ese elemento html.
Zlatin Zlatev
1
En texto normal, los caracteres de escape son raros. Es mejor llamar a reemplazar solo cuando sea necesario, si le importa la velocidad máxima:if (/[<>&"]/.test(str) { ... }
Vitaly
3
@callum: No. No estoy interesado en enumerar casos en los que creo que "algo podría salir mal" (sobre todo porque son los casos inesperados / olvidados los que te lastimarán, y cuando menos te lo esperes). Estoy interesado en codificar según los estándares (por lo que los casos inesperados / olvidados no pueden dañarlo por definición ). No puedo enfatizar lo importante que es esto. >es un carácter especial en HTML, así que escápalo. Simple como eso. :)
Lightness Races in Orbit
4
@LightnessRacesinOrbit Es relevante porque la pregunta es cuál es el método más rápido posible. Si es posible omitir el >reemplazo, eso lo haría más rápido.
callum
104

Aquí tienes una forma de hacer esto:

var escape = document.createElement('textarea');
function escapeHTML(html) {
    escape.textContent = html;
    return escape.innerHTML;
}

function unescapeHTML(html) {
    escape.innerHTML = html;
    return escape.textContent;
}

Aquí tienes una demostración.

Diseñador web
fuente
Rediseño de la demostración. Aquí hay una versión de pantalla completa: jsfiddle.net/Daniel_Hug/qPUEX/show/light
Web_Designer
13
No estoy seguro de cómo / qué / por qué, pero esto es genial.
rob_james
4
Parece que está aprovechando el código existente del elemento TextArea para escapar del texto literal. Muy lindo, creo que este pequeño truco va a buscar otro hogar.
Ajax
3
@jazkat No estoy usando esa función. La variable de escape que uso, me defino en el ejemplo.
Web_Designer
2
pero ¿pierde espacio en blanco, etc.?
Andrew
31

El método de Martijn como función prototipo:

String.prototype.escape = function() {
    var tagsToReplace = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;'
    };
    return this.replace(/[&<>]/g, function(tag) {
        return tagsToReplace[tag] || tag;
    });
};

var a = "<abc>";
var b = a.escape(); // "&lt;abc&gt;"
Aram Kocharyan
fuente
12
Agregue a Stringesto, debería ser escapeHtml ya que no es un escape para una cadena en general. Eso es String.escapeHtmlcorrecto, pero String.escapeplantea la pregunta, "¿escapar para qué?"
Lawrence Dol
3
Sí buena idea. Me he alejado de ampliar el prototipo estos días para evitar conflictos.
Aram Kocharyan
1
Si su navegador es compatible con Symbol, puede usarlo en su lugar para evitar contaminar el espacio de nombres de clave de cadena. var escape = new Symbol ("escape"); String.prototype [escape] = function () {...}; "texto" [escape] ();
Ajax
12

Una solución aún más rápida / más corta es:

escaped = new Option(html).innerHTML

Esto está relacionado con un extraño vestigio de JavaScript mediante el cual el elemento Option retiene un constructor que hace este tipo de escape automáticamente.

Crédito a https://github.com/jasonmoo/t.js/blob/master/t.js

Todd
fuente
1
Una sola línea ordenada pero el método más lento después de la expresión regular. Además, el texto aquí puede tener espacios en blanco eliminados, de acuerdo con la especificación
ShortFuse
Tenga en cuenta que el enlace del "método más lento" de @ ShortFuse hace que mi sistema se quede sin RAM (con ~ 6GB libres) y Firefox parece dejar de asignar justo antes de que se agote la memoria, por lo que en lugar de matar el proceso ofensivo, Linux se quedará allí y le permitirá hacerlo. un duro apagado.
Luc
11

El código fuente de AngularJS también tiene una versión dentro de angular-sanitize.js .

var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
    // Match everything outside of normal chars and " (quote character)
    NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
/**
 * Escapes all potentially dangerous characters, so that the
 * resulting string can be safely inserted into attribute or
 * element text.
 * @param value
 * @returns {string} escaped text
 */
function encodeEntities(value) {
  return value.
    replace(/&/g, '&amp;').
    replace(SURROGATE_PAIR_REGEXP, function(value) {
      var hi = value.charCodeAt(0);
      var low = value.charCodeAt(1);
      return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
    }).
    replace(NON_ALPHANUMERIC_REGEXP, function(value) {
      return '&#' + value.charCodeAt(0) + ';';
    }).
    replace(/</g, '&lt;').
    replace(/>/g, '&gt;');
}
Kevin Hakanson
fuente
1
Vaya, esa expresión regular sin alfanum es intensa. No creo que el | en la expresión es necesario sin embargo.
Ajax
9

Secuencia de comandos todo en uno:

// HTML entities Encode/Decode

function htmlspecialchars(str) {
    var map = {
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        "\"": "&quot;",
        "'": "&#39;" // ' -> &apos; for XML only
    };
    return str.replace(/[&<>"']/g, function(m) { return map[m]; });
}
function htmlspecialchars_decode(str) {
    var map = {
        "&amp;": "&",
        "&lt;": "<",
        "&gt;": ">",
        "&quot;": "\"",
        "&#39;": "'"
    };
    return str.replace(/(&amp;|&lt;|&gt;|&quot;|&#39;)/g, function(m) { return map[m]; });
}
function htmlentities(str) {
    var textarea = document.createElement("textarea");
    textarea.innerHTML = str;
    return textarea.innerHTML;
}
function htmlentities_decode(str) {
    var textarea = document.createElement("textarea");
    textarea.innerHTML = str;
    return textarea.value;
}

http://pastebin.com/JGCVs0Ts

baptx
fuente
No voté negativamente, pero todos los reemplazos de estilo de expresiones regulares no podrán codificar unicode ... Por lo tanto, cualquiera que use un idioma extranjero se sentirá decepcionado. El truco <textarea> mencionado anteriormente es realmente genial y maneja todo de forma rápida y segura.
Ajax
La expresión regular me funciona bien con varios caracteres Unicode no latinos. No esperaría nada más. ¿Cómo crees que esto no funcionaría? ¿Está pensando en páginas de códigos de un solo byte que requieren entidades HTML? Para eso sirven la 3ª y 4ª función, y explícitamente no la 1ª y la 2ª. Me gusta la diferenciación.
ygoe
@LonelyPixel No creo que vea tu comentario si no lo mencionas ("Solo se puede notificar a un usuario adicional; siempre se notificará al propietario de la publicación")
baptx
No sabía que existían notificaciones específicas. @Ajax, por favor vea mi comentario arriba.
ygoe
@LonelyPixel que veo ahora. Por alguna razón, no pensé que hubiera un reemplazo de estilo de área de texto en esta respuesta. De hecho, estaba pensando en valores unicode grandes de doble punto de código, como el mandarín. Quiero decir, sería posible hacer que una expresión regular sea lo suficientemente inteligente, pero cuando miras los atajos que pueden tomar los proveedores de navegadores, me sentiría bastante bien apostando a que el área de texto será mucho más rápido (que una expresión regular completamente competente). ¿Alguien publicó un punto de referencia en esta respuesta? Juré que había visto uno.
Ajax
2

function encode(r) {
  return r.replace(/[\x26\x0A\x3c\x3e\x22\x27]/g, function(r) {
	return "&#" + r.charCodeAt(0) + ";";
  });
}

test.value=encode('How to encode\nonly html tags &<>\'" nice & fast!');

/*
 \x26 is &ampersand (it has to be first),
 \x0A is newline,
 \x22 is ",
 \x27 is ',
 \x3c is <,
 \x3e is >
*/
<textarea id=test rows=11 cols=55>www.WHAK.com</textarea>

Dave Brown
fuente
1

No estoy del todo seguro acerca de la velocidad, pero si está buscando simplicidad, sugeriría usar la función de escape lodash / underscore .

gilmatic
fuente
0

El método de Martijn como función única con el manejo de la marca " ( usando en javascript ):

function escapeHTML(html) {
    var fn=function(tag) {
        var charsToReplace = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&#34;'
        };
        return charsToReplace[tag] || tag;
    }
    return html.replace(/[&<>"]/g, fn);
}
iman
fuente
0

Agregaré XMLSerializera la pila. Proporciona el resultado más rápido sin utilizar ningún objeto de almacenamiento en caché (ni en el serializador ni en el nodo de texto).

function serializeTextNode(text) {
  return new XMLSerializer().serializeToString(document.createTextNode(text));
}

La ventaja adicional es que admite atributos que se serializan de manera diferente a los nodos de texto:

function serializeAttributeValue(value) {
  const attr = document.createAttribute('a');
  attr.value = value;
  return new XMLSerializer().serializeToString(attr);
}

Puede ver lo que realmente está reemplazando al verificar la especificación, tanto para los nodos de texto como para los valores de los atributos . La documentación completa tiene más tipos de nodos, pero el concepto es el mismo.

En cuanto al rendimiento, es el más rápido cuando no se almacena en caché. Cuando permite el almacenamiento en caché, la llamada innerHTMLa un elemento HTMLElement con un nodo Text secundario es más rápida. Regex sería el más lento (como lo demuestran otros comentarios). Por supuesto, XMLSerializer podría ser más rápido en otros navegadores, pero en mis pruebas (limitadas), a innerHTMLes más rápido.


Línea única más rápida:

new XMLSerializer().serializeToString(document.createTextNode(text));

Más rápido con el almacenamiento en caché:

const cachedElementParent = document.createElement('div');
const cachedChildTextNode = document.createTextNode('');
cachedElementParent.appendChild(cachedChildTextNode);

function serializeTextNode(text) {
  cachedChildTextNode.nodeValue = text;
  return cachedElementParent.innerHTML;
}

https://jsperf.com/htmlentityencode/1

Mecha corta
fuente
-3

Un poco tarde para el programa, pero ¿qué pasa con el uso de encodeURIComponent () y decodeURIComponent () ?

suncat100
fuente
1
Esos hacen algo completamente ajeno
callum
1
Quizás el mayor abuso de la palabra "completamente" que jamás haya escuchado. Por ejemplo, en relación con la pregunta del tema principal, podría usarse para decodificar una cadena html (obviamente por alguna razón de almacenamiento), independientemente de las etiquetas html, y luego volver a codificarla fácilmente en html cuando sea necesario.
suncat100