La codificación HTML se pierde cuando se lee el atributo del campo de entrada


745

Estoy usando JavaScript para extraer un valor de un campo oculto y mostrarlo en un cuadro de texto. El valor en el campo oculto está codificado.

Por ejemplo,

<input id='hiddenId' type='hidden' value='chalk &amp; cheese' />

queda atrapado en

<input type='text' value='chalk &amp; cheese' />

a través de algún jQuery para obtener el valor del campo oculto (es en este punto cuando pierdo la codificación):

$('#hiddenId').attr('value')

El problema es que cuando leo chalk &amp; cheesedesde el campo oculto, JavaScript parece perder la codificación. No quiero que sea el valor chalk & cheese. Quiero amp;que se conserve el literal .

¿Hay una biblioteca de JavaScript o un método jQuery que codifique HTML una cadena?


¿Puedes mostrar el Javascript que estás usando?
Sinan Taifour

1
he agregado cómo obtengo valor del campo oculto
AJM

55
NO use el método innerHTML (el método jQuery .html () usa innerHTML), ya que en algunos navegadores (solo he probado Chrome), esto no escapará a las comillas, por lo que si tuviera que poner su valor en un valor de atributo , terminarías con una vulnerabilidad XSS.
James Roper el

21
en qué contexto es chalky cheesealguna vez se usan juntos 0_o
d -_- b

2
@d -_- b al comparar dos elementos. ejemplo. son tan diferentes como la tiza y el queso;)
Anurag

Respuestas:


1067

EDITAR: Esta respuesta fue publicada hace mucho tiempo, y la htmlDecodefunción introdujo una vulnerabilidad XSS. Se ha modificado cambiando el elemento temporal de diva a textareareduciendo la posibilidad de XSS. Pero hoy en día, le animo a usar la API DOMParser como se sugiere en otra respuesta .


Yo uso estas funciones:

function htmlEncode(value){
  // Create a in-memory element, set its inner text (which is automatically encoded)
  // Then grab the encoded contents back out. The element never exists on the DOM.
  return $('<textarea/>').text(value).html();
}

function htmlDecode(value){
  return $('<textarea/>').html(value).text();
}

Básicamente, un elemento de área de texto se crea en la memoria, pero nunca se agrega al documento.

En la htmlEncodefunción configuro el innerTextdel elemento y recupero el codificado innerHTML; en la htmlDecodefunción configuro el innerHTMLvalor del elemento y innerTextse recupera.

Mira un ejemplo en ejecución aquí .


95
Esto funciona para la mayoría de los escenarios, pero esta implementación de htmlDecode eliminará cualquier espacio en blanco adicional. Entonces, para algunos valores de "input", input! = HtmlDecode (htmlEncode (input)). Esto fue un problema para nosotros en algunos escenarios. Por ejemplo, si input = "<p> \ t Hi \ n There </p>", una codificación / decodificación de ida y vuelta producirá "<p> Hi There </p>". La mayoría de las veces esto está bien, pero a veces no lo está. :)
Pettys

77
Gracias por la solucion! Resolví el problema de eliminar el espacio en blanco adicional reemplazando nuevas líneas con %% NL %% en el valor de texto, luego llamé .html () para obtener el valor codificado en HTML, luego reemplacé %% NL %% con <br /> ' s ... No es a prueba de balas, pero funcionó y no era probable que mis usuarios escribieran %% NL %%.
benno

1
Lo curioso es que CSS tiene una white-spacepropiedad, que sugiere cómo se deben procesar los espacios en el contenido HTML. La presencia de la propiedad implica que "esto está preformateado, se deben preservar los espacios y los saltos de línea". Esto rompe la separación de estilo y contenido, porque si intentas reformatear el HTML para que sea "bonito" o lo vuelves a través de un ciclo de codificación / decodificación como este, entonces se reducen las series de espacios / interrupciones, y el codificador no tiene manera de saber si estuvo bien hacerlo, porque no conoce el white-space:pre-*;indicador en un archivo CSS externo.
Triynko

2
Esta solución podría depender de si la página está escrita como html o xhtml, por lo que preferiría una solución que no implique el DOM.
Phil H

30
Aunque fue respondida dos años después, la respuesta de @Anentropic a continuación es mejor en todos los sentidos.
chad

559

El truco jQuery no codifica comillas y en IE eliminará su espacio en blanco.

Basado en la etiqueta de escape de Django, que supongo que ya está muy utilizada / probada, hice esta función que hace lo que se necesita.

Podría decirse que es más simple (y posiblemente más rápido) que cualquiera de las soluciones para el problema de eliminación de espacios en blanco, y codifica comillas, lo cual es esencial si va a utilizar el resultado dentro de un valor de atributo, por ejemplo.

function htmlEscape(str) {
    return str
        .replace(/&/g, '&amp;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');
}

// I needed the opposite function today, so adding here too:
function htmlUnescape(str){
    return str
        .replace(/&quot;/g, '"')
        .replace(/&#39;/g, "'")
        .replace(/&lt;/g, '<')
        .replace(/&gt;/g, '>')
        .replace(/&amp;/g, '&');
}

Actualización 2013-06-17:
En la búsqueda del escape más rápido, he encontrado esta implementación de un replaceAllmétodo:
http://dumpsite.com/forum/index.php?topic=4.msg29#msg29
(también referenciado aquí: Más rápido método para reemplazar todas las instancias de un carácter en una cadena )
Algunos resultados de rendimiento aquí:
http://jsperf.com/htmlencoderegex/25

Le da una cadena de resultados idéntica a las replacecadenas integradas de arriba. ¡¿Sería muy feliz si alguien pudiera explicar por qué es más rápido ?!

Actualización 2015-03-04:
Acabo de notar que AngularJS está utilizando exactamente el método anterior:
https://github.com/angular/angular.js/blob/v1.3.14/src/ngSanitize/sanitize.js#L435

Añaden un par de refinamientos: parecen estar manejando un oscuro problema de Unicode , así como convirtiendo todos los caracteres no alfanuméricos en entidades. Tenía la impresión de que esto último no era necesario siempre que tenga un juego de caracteres UTF8 especificado para su documento.

Notaré que (4 años después) Django todavía no hace ninguna de estas cosas, así que no estoy seguro de cuán importantes son:
https://github.com/django/django/blob/1.8b1/django/utils /html.py#L44

Actualización 2016-04-06:
También puede desear escapar de la barra diagonal /. Esto no es necesario para la codificación HTML correcta, sin embargo, OWASP lo recomienda como una medida de seguridad anti-XSS. (gracias a @JNF por sugerir esto en los comentarios)

        .replace(/\//g, '&#x2F;');

3
También puede usar en &apos;lugar de&#39;
Ferruccio


55
Gracias, nunca me di cuenta de que &apos;no es una entidad HTML válida.
Ferruccio

10
Sin el /g, .replace()solo se reemplazará el primer partido.
ThinkingStiff

1
@ Tracker1 No estoy de acuerdo, si la función recibe una entrada no válida, debería arrojar un error. Si en un caso de uso específico desea manejar una entrada no válida de esa manera, compruebe el valor antes de llamar a la función o ajuste la llamada a la función en un intento / captura.
Anentropic

80

Aquí hay una versión que no es jQuery que es considerablemente más rápida que la .html()versión jQuery y la .replace()versión. Esto conserva todo el espacio en blanco, pero al igual que la versión jQuery, no maneja las comillas.

function htmlEncode( html ) {
    return document.createElement( 'a' ).appendChild( 
        document.createTextNode( html ) ).parentNode.innerHTML;
};

Velocidad: http://jsperf.com/htmlencoderegex/17

prueba de velocidad

Manifestación: jsFiddle

Salida:

salida

Guión:

function htmlEncode( html ) {
    return document.createElement( 'a' ).appendChild( 
        document.createTextNode( html ) ).parentNode.innerHTML;
};

function htmlDecode( html ) {
    var a = document.createElement( 'a' ); a.innerHTML = html;
    return a.textContent;
};

document.getElementById( 'text' ).value = htmlEncode( document.getElementById( 'hidden' ).value );

//sanity check
var html = '<div>   &amp; hello</div>';
document.getElementById( 'same' ).textContent = 
      'html === htmlDecode( htmlEncode( html ) ): ' 
    + ( html === htmlDecode( htmlEncode( html ) ) );

HTML:

<input id="hidden" type="hidden" value="chalk    &amp; cheese" />
<input id="text" value="" />
<div id="same"></div>

17
Esto plantea la pregunta: ¿por qué ya no es una función global en JS?
SEOF

2
la .replace()versión no regex sugerida recientemente por @SEoF resulta ser enormemente más rápida: jsperf.com/htmlencoderegex/22
Anentropic

@Anentropic Eso sí que se enciende rápido, pero no creo que esté funcionando. Sin /g, .replace()solo está haciendo el primer partido.
ThinkingStiff

Curiosamente en Firefox que puede hacer replace('a', 'b', 'g')lo que funciona igual que replace(/a/g, 'b')... la velocidad es idéntica demasiado sin embargo
Anentropic

1
yo tampoco :) Empecé solo queriendo manejar comillas y terminé en una búsqueda de velocidad ...
Anentropic

32

Sé que es una pregunta antigua, pero quería publicar una variación de la respuesta aceptada que funcionará en IE sin eliminar líneas:

function multiLineHtmlEncode(value) {
    var lines = value.split(/\r\n|\r|\n/);
    for (var i = 0; i < lines.length; i++) {
        lines[i] = htmlEncode(lines[i]);
    }
    return lines.join('\r\n');
}

function htmlEncode(value) {
    return $('<div/>').text(value).html();
} 


12

Buena respuesta. Tenga en cuenta que si el valor a codificar es undefinedo nullcon jQuery 1.4.2, puede obtener errores como:

jQuery("<div/>").text(value).html is not a function

O

Uncaught TypeError: Object has no method 'html'

La solución es modificar la función para verificar un valor real:

function htmlEncode(value){ 
    if (value) {
        return jQuery('<div/>').text(value).html(); 
    } else {
        return '';
    }
}

8
jQuery('<div/>').text(value || '').html()
Roufamatic

3
@roufamatic - Bonito one-liner. Pero verificar si no está vacío valuecon un ifguardado tiene que crear un DIV sobre la marcha y obtener su valor. Esto puede ser mucho más eficaz si htmlEncodese llama mucho Y si es probable que valueesté vacío.
leepowers

Hola, no hace β a & beta ¿sabes por qué?
Dilip Rajkumar

11

Para aquellos que prefieren JavaScript simple, este es el método que he usado con éxito:

function escapeHTML (str)
{
    var div = document.createElement('div');
    var text = document.createTextNode(str);
    div.appendChild(text);
    return div.innerHTML;
}

6

FWIW, la codificación no se está perdiendo. La codificación es utilizada por el analizador de marcado (navegador) durante la carga de la página. Una vez que la fuente se lee y analiza y el navegador tiene el DOM cargado en la memoria, la codificación se analiza en lo que representa. Entonces, cuando su JS se ejecuta para leer algo en la memoria, el carácter que obtiene es lo que representa la codificación.

Puedo estar operando estrictamente en semántica aquí, pero quería que entendieras el propósito de la codificación. La palabra "perdido" hace que parezca que algo no funciona como debería.


6

Más rápido sin Jquery. Puedes codificar todos los caracteres de tu cadena:

function encode(e){return e.replace(/[^]/g,function(e){return"&#"+e.charCodeAt(0)+";"})}

O simplemente apunte a los personajes principales de los que preocuparse (&, inebreaks, <,>, "y ') como:

function encode(r){
return r.replace(/[\x26\x0A\<>'"]/g,function(r){return"&#"+r.charCodeAt(0)+";"})
}

test.value=encode('Encode HTML entities!\n\n"Safe" escape <script id=\'\'> & useful in <pre> tags!');

testing.innerHTML=test.value;

/*************
* \x26 is &ampersand (it has to be first),
* \x0A is newline,
*************/
<textarea id=test rows="9" cols="55"></textarea>

<div id="testing">www.WHAK.com</div>


5

Prototype lo tiene incorporado en la clase String . Entonces, si está usando / planea usar Prototype, hace algo como:

'<div class="article">This is an article</div>'.escapeHTML();
// -> "&lt;div class="article"&gt;This is an article&lt;/div&gt;"

99
Después de mirar la solución de Prototype, esto es todo lo que está haciendo ... .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); Bastante fácil.
Steve Wortham

55
¿No debería hacer algo con comillas también? eso no es bueno
Anentropic

@Anentropic No veo por qué tendría que hacer algo con comillas; ya que las comillas no necesitan escaparse a menos que estén dentro de un valor de atributo.
Andy

Bien, después de reflexionar, retomo ese comentario: si está construyendo una pieza de HTML, desearía codificar cada parte, incluidos los valores de los atributos, por lo que estoy de acuerdo con Anentropic y no creo que la función Prototypejs sea suficiente en Ese caso.
Andy

4

Aquí hay una solución javascript simple. Extiende el objeto String con un método "HTMLEncode" que se puede usar en un objeto sin parámetro o con un parámetro.

String.prototype.HTMLEncode = function(str) {
  var result = "";
  var str = (arguments.length===1) ? str : this;
  for(var i=0; i<str.length; i++) {
     var chrcode = str.charCodeAt(i);
     result+=(chrcode>128) ? "&#"+chrcode+";" : str.substr(i,1)
   }
   return result;
}
// TEST
console.log("stetaewteaw æø".HTMLEncode());
console.log("stetaewteaw æø".HTMLEncode("æåøåæå"))

He hecho un "método HTMLEncode para javascript" .


3

Basado en la desinfección de angular ... (sintaxis del módulo es6)

// ref: https://github.com/angular/angular.js/blob/v1.3.14/src/ngSanitize/sanitize.js
const SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
const NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;

const decodeElem = document.createElement('pre');


/**
 * Decodes html encoded text, so that the actual string may
 * be used.
 * @param value
 * @returns {string} decoded text
 */
export function decode(value) {
  if (!value) return '';
  decodeElem.innerHTML = value.replace(/</g, '&lt;');
  return decodeElem.textContent;
}


/**
 * Encodes all potentially dangerous characters, so that the
 * resulting string can be safely inserted into attribute or
 * element text.
 * @param value
 * @returns {string} encoded text
 */
export function encode(value) {
  if (value === null || value === undefined) return '';
  return String(value).
    replace(/&/g, '&amp;').
    replace(SURROGATE_PAIR_REGEXP, value => {
      var hi = value.charCodeAt(0);
      var low = value.charCodeAt(1);
      return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
    }).
    replace(NON_ALPHANUMERIC_REGEXP, value => {
      return '&#' + value.charCodeAt(0) + ';';
    }).
    replace(/</g, '&lt;').
    replace(/>/g, '&gt;');
}

export default {encode,decode};

Si bien realmente me gusta esta respuesta y realmente creo que es un buen enfoque, tengo una duda, ¿es el operador bit a bit en if (value === null | value === undefined) return '';un error tipográfico o en realidad una característica? Si es así, ¿por qué usar ese y no el común ||? ¡¡Gracias!!
Alejandro Vales

1
@AlejandroVales Estoy bastante seguro de que fue un error tipográfico ... corregido.
Rastreador1

1
Bueno, de todos modos tenga en cuenta que el | conducirá a 0 o 1, así que en realidad funcionó ^^
Alejandro Vales

¿No podrías simplemente usar == null? undefinedes lo único con lo que tenemos equivalencia null, por lo que no son necesarios dos triples iguales de todos modos
Hashbrown

eso no es cierto en absoluto. nully 0ambos son falsos, sí, así que no puedes hacerlo !value, pero el objetivo ==es facilitar ciertas cosas. 0 == nullEs falso. undefined == nulles verdad. puedes hacervalue == null
Hashbrown

3

Hasta donde sé, no hay ningún método de codificación / decodificación HTML directo en javascript.

Sin embargo, lo que puede hacer es usar JS para crear un elemento arbitrario, establecer su texto interno y luego leerlo usando innerHTML.

Digamos, con jQuery, esto debería funcionar:

var helper = $('chalk & cheese').hide().appendTo('body');
var htmled = helper.html();
helper.remove();

O algo por el estilo.


Encuentro el voto negativo un poco divertido, teniendo en cuenta que esta respuesta es casi idéntica a la que tiene más de 870 votos positivos, y se publicó un poco después de esta.
Ken Egozi

2

No debería tener que escapar / codificar valores para transferirlos de un campo de entrada a otro.

<form>
 <input id="button" type="button" value="Click me">
 <input type="hidden" id="hiddenId" name="hiddenId" value="I like cheese">
 <input type="text" id="output" name="output">
</form>
<script>
    $(document).ready(function(e) {
        $('#button').click(function(e) {
            $('#output').val($('#hiddenId').val());
        });
    });
</script>

JS no va insertando HTML sin formato ni nada; solo le dice al DOM que establezca la valuepropiedad (o atributo; no estoy seguro). De cualquier manera, el DOM maneja cualquier problema de codificación por usted. A menos que esté haciendo algo extraño como usar document.writeo eval, la codificación HTML será efectivamente transparente.

Si está hablando de generar un nuevo cuadro de texto para contener el resultado ... sigue siendo tan fácil. Simplemente pase la parte estática del HTML a jQuery, y luego configure el resto de las propiedades / atributos en el objeto que le devuelve.

$box = $('<input type="text" name="whatever">').val($('#hiddenId').val());

2

Tuve un problema similar y lo solucioné usando la función encodeURIComponentde JavaScript ( documentación )

Por ejemplo, en su caso si usa:

<input id='hiddenId' type='hidden' value='chalk & cheese' />

y

encodeURIComponent($('#hiddenId').attr('value'))

obtendrá chalk%20%26%20cheese. Incluso los espacios se mantienen.

En mi caso, tuve que codificar una barra invertida y este código funciona perfectamente

encodeURIComponent('name/surname')

y tengo name%2Fsurname


2

Aquí hay un poco que emula la Server.HTMLEncodefunción de la ASP de Microsoft, escrita en JavaScript puro:

function htmlEncode(s) {
  var ntable = {
    "&": "amp",
    "<": "lt",
    ">": "gt",
    "\"": "quot"
  };
  s = s.replace(/[&<>"]/g, function(ch) {
    return "&" + ntable[ch] + ";";
  })
  s = s.replace(/[^ -\x7e]/g, function(ch) {
    return "&#" + ch.charCodeAt(0).toString() + ";";
  });
  return s;
}

El resultado no codifica apóstrofes, pero codifica los otros especiales HTML y cualquier carácter fuera del rango 0x20-0x7e.


2

Mi función pure-JS:

/**
 * HTML entities encode
 *
 * @param {string} str Input text
 * @return {string} Filtered text
 */
function htmlencode (str){

  var div = document.createElement('div');
  div.appendChild(document.createTextNode(str));
  return div.innerHTML;
}

JavaScript HTML Entities Encode & Decode


1

Si quieres usar jQuery. Encontré esto:

http://www.jquerysdk.com/api/jQuery.htmlspecialchars

(parte del complemento jquery.string ofrecido por jQuery SDK)

Creo que el problema con Prototype es que extiende los objetos base en JavaScript y será incompatible con cualquier jQuery que haya utilizado. Por supuesto, si ya está utilizando Prototype y no jQuery, no será un problema.

EDITAR: También existe esto, que es un puerto de las utilidades de cadena de Prototype para jQuery:

http://stilldesigning.com/dotstring/


1
var htmlEnDeCode = (function() {
    var charToEntityRegex,
        entityToCharRegex,
        charToEntity,
        entityToChar;

    function resetCharacterEntities() {
        charToEntity = {};
        entityToChar = {};
        // add the default set
        addCharacterEntities({
            '&amp;'     :   '&',
            '&gt;'      :   '>',
            '&lt;'      :   '<',
            '&quot;'    :   '"',
            '&#39;'     :   "'"
        });
    }

    function addCharacterEntities(newEntities) {
        var charKeys = [],
            entityKeys = [],
            key, echar;
        for (key in newEntities) {
            echar = newEntities[key];
            entityToChar[key] = echar;
            charToEntity[echar] = key;
            charKeys.push(echar);
            entityKeys.push(key);
        }
        charToEntityRegex = new RegExp('(' + charKeys.join('|') + ')', 'g');
        entityToCharRegex = new RegExp('(' + entityKeys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
    }

    function htmlEncode(value){
        var htmlEncodeReplaceFn = function(match, capture) {
            return charToEntity[capture];
        };

        return (!value) ? value : String(value).replace(charToEntityRegex, htmlEncodeReplaceFn);
    }

    function htmlDecode(value) {
        var htmlDecodeReplaceFn = function(match, capture) {
            return (capture in entityToChar) ? entityToChar[capture] : String.fromCharCode(parseInt(capture.substr(2), 10));
        };

        return (!value) ? value : String(value).replace(entityToCharRegex, htmlDecodeReplaceFn);
    }

    resetCharacterEntities();

    return {
        htmlEncode: htmlEncode,
        htmlDecode: htmlDecode
    };
})();

Esto es del código fuente ExtJS.


1
<script>
String.prototype.htmlEncode = function () {
    return String(this)
        .replace(/&/g, '&amp;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');

}

var aString = '<script>alert("I hack your site")</script>';
console.log(aString.htmlEncode());
</script>

Saldrá: &lt;script&gt;alert(&quot;I hack your site&quot;)&lt;/script&gt;

.htmlEncode () estará accesible en todas las cadenas una vez definidas.


1

HTML codifica el valor dado

  var htmlEncodeContainer = $('<div />');
  function htmlEncode(value) {
    if (value) {
      return htmlEncodeContainer.text(value).html();
    } else {
      return '';
    }
  }


0

Elegir lo que escapeHTML()está haciendo en prototype.js

Agregar este script te ayuda a escapar de HTML:

String.prototype.escapeHTML = function() { 
    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
}

ahora puede llamar al método escapeHTML en cadenas en su script, como:

var escapedString = "<h1>this is HTML</h1>".escapeHTML();
// gives: "&lt;h1&gt;this is HTML&lt;/h1&gt;"

Espero que ayude a cualquiera que busque una solución simple sin tener que incluir todo el prototipo.js


0

Usando algunas de las otras respuestas aquí, hice una versión que reemplaza todos los caracteres pertinentes en una sola pasada, independientemente de la cantidad de caracteres codificados distintos (solo una llamada a replace() ), por lo que será más rápido para cadenas más grandes.

No depende de la API DOM para existir o de otras bibliotecas.

window.encodeHTML = (function() {
    function escapeRegex(s) {
        return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
    }
    var encodings = {
        '&'  : '&amp;',
        '"'  : '&quot;',
        '\'' : '&#39;',
        '<'  : '&lt;',
        '>'  : '&gt;',
        '\\' : '&#x2F;'
    };
    function encode(what) { return encodings[what]; };
    var specialChars = new RegExp('[' +
        escapeRegex(Object.keys(encodings).join('')) +
    ']', 'g');

    return function(text) { return text.replace(specialChars, encode); };
})();

Habiendo corrido eso una vez, ahora puedes llamar

encodeHTML('<>&"\'')

Llegar &lt;&gt;&amp;&quot;&#39;


0

function encodeHTML(str) {
    return document.createElement("a").appendChild( 
        document.createTextNode(str)).parentNode.innerHTML;
};

function decodeHTML(str) {
    var element = document.createElement("a"); 
    element.innerHTML = str;
    return element.textContent;
};
var str = "<"
var enc = encodeHTML(str);
var dec = decodeHTML(enc);
console.log("str: " + str, "\nenc: " + enc, "\ndec: " + dec);

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.