HtmlSpecialChars equivalente en Javascript?

167

Aparentemente, esto es más difícil de encontrar de lo que pensé que sería. E incluso es tan simple ...

¿Existe una función equivalente a los htmlspecialchars de PHP integrados en Javascript? Sé que es bastante fácil implementarlo usted mismo, pero usar una función incorporada, si está disponible, es mejor.

Para aquellos que no están familiarizados con PHP, htmlspecialchars traduce cosas como <htmltag/>a&lt;htmltag/&gt;

Lo sé escape()y encodeURI()no trabajo de esta manera.

Bart van Heukelom
fuente
php tiene algunas herramientas realmente buenas, var_dump, print_r, htmlspecialchars, etc. Desafortunadamente, sospecho que no es lo mismo con js. js alert es muy pobre. Una forma rápida de ver que viene una cadena inesperada (e invisible en el cuadro de alerta) es alertar la longitud de la cadena en lugar de la cadena en sí misma.
Melsi
Posible duplicado de cadenas HTML
nhahtdh
Ver stackoverflow.com/a/12034334/8804293 , tiene una gran respuesta
Elijah Mock,

Respuestas:

330

Hay un problema con su código de solución: solo escapará a la primera aparición de cada carácter especial. Por ejemplo:

escapeHtml('Kip\'s <b>evil</b> "test" code\'s here');
Actual:   Kip&#039;s &lt;b&gt;evil</b> &quot;test" code's here
Expected: Kip&#039;s &lt;b&gt;evil&lt;/b&gt; &quot;test&quot; code&#039;s here

Aquí hay un código que funciona correctamente:

function escapeHtml(text) {
  return text
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
}

Actualizar

El siguiente código producirá resultados idénticos a los anteriores, pero funciona mejor, particularmente en grandes bloques de texto (gracias jbo5112 ).

function escapeHtml(text) {
  var map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };
  
  return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}
Dormir
fuente
55
Lo bueno de esta función es que funciona en node.js que no tiene un dom por defecto
booyaa
66
Es más rápido usar una sola función de reemplazo y mapeo, y el reemplazo único se escala mucho mejor. ( jsperf.com/escape-html-special-chars/11 )
jbo5112
1
@ jbo5112 buen punto, no me di cuenta de que JS permitía devoluciones de llamada para reemplazo. Sin embargo, este código es más fácil de entender, y dudo que afeitarse unos pocos milisegundos de escapeHtml () va a hacer una diferencia a menos que lo llame cientos de veces seguidas por alguna razón.
Kip
Esto distorsionará las URL en el texto, lo que las hará inutilizables para complementos como Autolinker.js . ¿Hay alguna forma de abordar esto?
Radek Matěj
44
@ RadekMatěj Incluso en ese caso es perfectamente válido (preferible, diría yo) que ambos símbolos se codifiquen cuando se usan en un documento HTML. Todavía lo consideraría un error con el complemento.
Kip
31

Eso es codificación HTML. No hay una función nativa de JavaScript para hacer eso, pero puedes buscar en Google y obtener algunas muy bien hechas.

Por ejemplo, http://sanzon.wordpress.com/2008/05/01/neat-little-html-encoding-trick-in-javascript/

EDITAR:
Esto es lo que he probado:

var div = document.createElement('div');
  var text = document.createTextNode('<htmltag/>');
  div.appendChild(text);
  console.log(div.innerHTML);

Salida: &lt;htmltag/&gt;

OK W
fuente
Lástima, tendré que usar una función personalizada entonces.
Bart van Heukelom
Puedes probar el método en el enlace que he incluido en mi publicación. Muy buen concepto de hecho.
okw
@okw: Ok, primero te vinculaste a esto: yuki-onna.co.uk/html/encode.html que hace exactamente lo que encodeURIComponenthace y nada de lo que pidió el OP. Entonces, ¿puedes editar por favor? Parece que no puedo deshacer mi -1.
Crescent Fresh el
Sí, el código de esa página parece lógico pero no lo probé. Aunque el nuevo enlace funciona, lo he verificado yo mismo. Ya he actualizado la publicación hace algún tiempo.
okw
@BeauCielBleu: No. Los únicos nodos que se crean son un solo divelemento y un nodo de texto. Crear un nodo de texto con el texto `<img src = bogus onerror = alert (1337)>` solo creará un nodo de texto, no un imgelemento.
Tim Down
26

Vale la pena leerlo: http://bigdingus.com/2007/12/29/html-escaping-in-javascript/

escapeHTML: (function() {
 var MAP = {
   '&': '&amp;',
   '<': '&lt;',
   '>': '&gt;',
   '"': '&#34;',
   "'": '&#39;'
 };
  var repl = function(c) { return MAP[c]; };
  return function(s) {
    return s.replace(/[&<>'"]/g, repl);
  };
})()

Nota : solo ejecuta esto una vez. Y no lo ejecute en cadenas ya codificadas, por ejemplo, se &amp;convierte en&amp;amp;

Chris Jacob
fuente
3
Esta debería ser la respuesta aceptada y mejor votada. No estoy seguro de por qué no tuvo votos. Esta es la evaluación comparativa como la más rápida con una cadena de entrada larga (326 KB de búsqueda de Google) y corta en jsperf ( jsperf.com/escape-html-special-chars/11 ). Por favor vote esto.
jbo5112
¿Cuál es la diferencia entre ésta y la respuesta que obtuvo los votos más altos? ¿Por qué la función interna adicional ?. Una explicación podría ayudar a los usuarios a comprender mejor
Kosem
19

Con jQuery puede ser así:

var escapedValue = $('<div/>').text(value).html();

De la pregunta relacionada Escapar cadenas HTML con jQuery

Como se menciona en el comentario, las comillas dobles y las comillas simples se dejan como están para esta implementación. Eso significa que esta solución no debe usarse si necesita hacer un atributo de elemento como una cadena html sin procesar.

Alexander Yanovets
fuente
2
¿Alguna idea de si hay alguna sobrecarga en esto? ¿Agregar un objeto ficticio al DOM?
Kip
¿Y hay otras ventajas (por ejemplo, si tiene caracteres Unicode o algo así)?
Kip
44
Algo que encontré con esto: las comillas dobles y las comillas simples se dejan como están. Eso hace que esto sea problemático si desea usarlo en un valor de atributo.
Kip
1
Para pequeños fragmentos de texto, esto demora 30 veces más que ejecutar todos los reemplazos. Sin embargo, escala mejor. Con algo tan gigantesco como una página de resultados de búsqueda de Google (326 KB), es un 25-30% más rápido que reemplazar o hacer esto en JavaScript directo. Sin embargo, todos pierden constantemente debido a un solo reemplazo y una función de mapeo.
jbo5112
44
cómo vota la gente esta respuesta: la respuesta tiene jquery: +1 - NO escapa a comillas simples y dobles: ummmm .. (rascarse la cabeza) .. +1. <!-- Caps rage begin --> Esta respuesta debe tener un puntaje NEGATIVO ya que NO SE INCLUYE CERCA DE RESPONDER LA PREGUNTA "HtmlSpecialChars equivalente". <!-- Caps rage end -->It-does-not-escape-quotes-jesus-christ-and-other-deities. Dios mío, jquery personas.
Sharky
19

Aquí hay una función para escapar de HTML:

function escapeHtml(str)
{
    var map =
    {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#039;'
    };
    return str.replace(/[&<>"']/g, function(m) {return map[m];});
}

Y para decodificar:

function decodeHtml(str)
{
    var map =
    {
        '&amp;': '&',
        '&lt;': '<',
        '&gt;': '>',
        '&quot;': '"',
        '&#039;': "'"
    };
    return str.replace(/&amp;|&lt;|&gt;|&quot;|&#039;/g, function(m) {return map[m];});
}
Dan Bray
fuente
6

Underscore.js proporciona una función para esto:

_.escape(string)

Se escapa de una cadena para su inserción en HTML, reemplazando &, <,>, "y 'caracteres.

http://underscorejs.org/#escape

No es una función de Javascript incorporada, pero si ya está utilizando Underscore, es una mejor alternativa que escribir su propia función si sus cadenas para convertir no son demasiado grandes.

mer10z_tech
fuente
5

Otra toma de esto es renunciar a toda la asignación de caracteres por completo y, en cambio, convertir todos los caracteres no deseados en sus respectivas referencias de caracteres numéricos, por ejemplo:

function escapeHtml(raw) {
    return raw.replace(/[&<>"']/g, function onReplace(match) {
        return '&#' + match.charCodeAt(0) + ';';
    });
}

Tenga en cuenta que el RegEx especificado solo maneja los caracteres específicos de los que el OP quería escapar, pero, dependiendo del contexto en el que se utilizará el HTML escapado, estos caracteres pueden no ser suficientes. El artículo de Ryan Grove Hay más en el escape de HTML que &, <,> y " es una buena lectura sobre el tema. Y dependiendo de su contexto, el siguiente RegEx puede ser muy necesario para evitar la inyección de XSS:

var regex = /[&<>"'` !@$%()=+{}[\]]/g
Fredric
fuente
3
String.prototype.escapeHTML = function() {
        return this.replace(/&/g, "&amp;")
                   .replace(/</g, "&lt;")
                   .replace(/>/g, "&gt;")
                   .replace(/"/g, "&quot;")
                   .replace(/'/g, "&#039;");
    }

muestra:

var toto = "test<br>";
alert(toto.escapeHTML());
Patricio
fuente
3

Lo más probable es que no necesites esa función. Dado que su código ya está en el navegador *, puede acceder al DOM directamente en lugar de generar y codificar HTML que el navegador tendrá que decodificar hacia atrás para que se use realmente.

Use la innerTextpropiedad para insertar texto sin formato en el DOM de manera segura y mucho más rápida que con cualquiera de las funciones de escape presentadas. Incluso más rápido que asignar una cadena estática precodificada a innerHTML.

Se usa classListpara editar clases, datasetpara establecer data-atributos y setAttributepara otros.

Todos estos se encargarán de escapar por ti. Más precisamente, no se necesita escapar y no se realizará ninguna codificación debajo de **, ya que está trabajando con HTML, la representación textual de DOM.

// use existing element
var author = 'John "Superman" Doe <[email protected]>';
var el = document.getElementById('first');
el.dataset.author = author;
el.textContent = 'Author: '+author;

// or create a new element
var a = document.createElement('a');
a.classList.add('important');
a.href = '/search?q=term+"exact"&n=50';
a.textContent = 'Search for "exact" term';
document.body.appendChild(a);

// actual HTML code
console.log(el.outerHTML);
console.log(a.outerHTML);
.important { color: red; }
<div id="first"></div>

* Esta respuesta no está destinada a usuarios de JavaScript del lado del servidor (Node.js, etc. )

** A menos que lo conviertas explícitamente a HTML real después. Por ejemplo, al acceder innerHTML: esto es lo que sucede cuando ejecuta $('<div/>').text(value).html();sugerido en otras respuestas. Entonces, si su objetivo final es insertar algunos datos en el documento, al hacerlo de esta manera, hará el trabajo dos veces. También puede ver que en el HTML resultante no todo está codificado, solo el mínimo necesario para que sea válido. Se realiza en función del contexto, es por eso que este método jQuery no codifica comillas y, por lo tanto, no debe usarse como un escapista de propósito general. Se necesita el escape de cotizaciones cuando construye HTML como una cadena con datos que no son de confianza o que contienen comillas en el lugar del valor de un atributo. Si usa la API DOM, no tiene que preocuparse por escapar en absoluto.

usuario
fuente
¡Gracias por esto! He pasado mucho tiempo buscando una solución tan simple. Una cosa importante que he descubierto es que si su texto contiene nuevas líneas, tendrá que reemplazarlas con saltos de línea HTML (algo así como el.textContent = str; el.innerHTML = el.innerHTML.replace(/\n/g, '<br>')), o establecer la white-spacepropiedad CSS en preopre-wrap
stellatedHexahedron
@stellatedHexahedron, gracias por plantear este problema. He cambiado mi respuesta para recomendar en innerTextlugar de textContent. Si bien es un poco más lento y tiene algunas otras diferencias al leer la propiedad, es más intuitivo ya que hace el <br>reemplazo automáticamente cuando se le asigna.
usuario
2

Para los usuarios de Node.JS (o usuarios que utilizan el tiempo de ejecución de Jade en el navegador), puede usar la función de escape de Jade.

require('jade').runtime.escape(...);

No tiene sentido escribirlo usted mismo si alguien más lo está manteniendo. :)

BMiner
fuente
1

Estoy elaborando un poco la respuesta de okw.

Puede usar las funciones DOM del navegador para eso.

var utils = {
    dummy: document.createElement('div'),
    escapeHTML: function(s) {
        this.dummy.textContent = s
        return this.dummy.innerHTML
    }
}

utils.escapeHTML('<escapeThis>&')

Esto vuelve &lt;escapeThis&gt;&amp;

Utiliza la función estándar createElementpara crear un elemento invisible, luego usa la función textContentpara establecer cualquier cadena como su contenido y luego innerHTMLpara obtener el contenido en su representación HTML.

Jonas Eberle
fuente
0
function htmlspecialchars(str) {
 if (typeof(str) == "string") {
  str = str.replace(/&/g, "&amp;"); /* must do &amp; first */
  str = str.replace(/"/g, "&quot;");
  str = str.replace(/'/g, "&#039;");
  str = str.replace(/</g, "&lt;");
  str = str.replace(/>/g, "&gt;");
  }
 return str;
 }

fuente
0

Espero que esto gane la carrera debido a su rendimiento y lo más importante no a una lógica encadenada usando .replace ('&', '&'). Replace ('<', '<') ...

var mapObj = {
   '&':"&amp;",
   '<':"&lt;",
   '>':"&gt;",
   '"':"&quot;",
   '\'':"&#039;"
};
var re = new RegExp(Object.keys(mapObj).join("|"),"gi");

function escapeHtml(str) 
{   
    return str.replace(re, function(matched)
    {
        return mapObj[matched.toLowerCase()];
    });
}

console.log('<script type="text/javascript">alert('Hello World');</script>');
console.log(escapeHtml('<script type="text/javascript">alert('Hello World');</script>'));
Aireado
fuente
0

Uno invertido:

function decodeHtml(text) {
    return text
        .replace(/&amp;/g, '&')
        .replace(/&lt;/ , '<')
        .replace(/&gt;/, '>')
        .replace(/&quot;/g,'"')
        .replace(/&#039;/g,"'");
}
Gleb Dolzikov
fuente
La pregunta no es cómo decodificar entidades. Esto hace lo contrario de lo que está preguntando la pregunta.
Quentin
Esto solo reemplazará las primeras instancias de &lt;y &gr;en una cadena.
Quentin
Esto solo decodificará los cinco caracteres que (fuera de los documentos que no son Unicode) deben escapar, no decodificará los que puedan escapar.
Quentin
Esto no tiene en cuenta las reglas para cuando el punto y coma es opcional.
Quentin
Si el HTML dice:, To write a greater than sign in HTML type &amp;gt;se mostrará incorrectamente en >lugar de&gt;
Quentin
0

OWASP recomienda que "[e] xcept para los caracteres alfanuméricos, [debe] escapar de todos los caracteres con valores ASCII inferiores a 256 con el&#xHH; formato (o una entidad con nombre si está disponible) para evitar el cambio de [un] atributo".

Así que aquí hay una función que hace eso, con un ejemplo de uso:

function escapeHTML(unsafe) {
  return unsafe.replace(
    /[\u0000-\u002F]|[\u003A-\u0040]|[\u005B-\u00FF]/g,
    c => '&#' + ('000' + c.charCodeAt(0)).substr(-4, 4) + ';'
  )
}
document.querySelector('div').innerHTML =
  '<span class=' +
  escapeHTML('this should break it! " | / % * + , - / ; < = > ^') +
  '>' +
  escapeHTML('<script>alert("inspect the attributes")\u003C/script>') +
  '</span>'
<div></div>

ADJenks
fuente
-1
function htmlEscape(str){
    return str.replace(/[&<>'"]/g,x=>'&#'+x.charCodeAt(0)+';')
}

Esta solución utiliza el código numérico de los caracteres, por ejemplo, <se reemplaza por&#60; .

Aunque su rendimiento es ligeramente peor que la solución que usa un mapa , tiene las ventajas:

  • No depende de una biblioteca o DOM
  • Bastante fácil de recordar (no necesita memorizar los 5 caracteres de escape HTML)
  • Pequeño código
  • Razonablemente rápido (sigue siendo más rápido que 5 reemplazos encadenados)
usuario202729
fuente