Codificação HTML perdida quando o atributo é lido do campo de entrada


745

Estou usando JavaScript para extrair um valor de um campo oculto e exibi-lo em uma caixa de texto. O valor no campo oculto é codificado.

Por exemplo,

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

é puxado para dentro

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

via algum jQuery para obter o valor do campo oculto (é nesse ponto que perco a codificação):

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

O problema é que, quando leio chalk &amp; cheesedo campo oculto, o JavaScript parece perder a codificação. Eu não quero que o valor seja chalk & cheese. Eu quero que o literal amp;seja retido.

Existe uma biblioteca JavaScript ou um método jQuery que codificará uma string em HTML?


Você pode mostrar o Javascript que você está usando?
Sinan Taifour 02/08/09

1
ter adicionado como eu obter o valor de campo oculto
AJM

5
NÃO use o método innerHTML (o método jQuery .html () usa innerHTML), pois em alguns navegadores (testei apenas o Chrome), isso não escapará de aspas; portanto, se você colocar seu valor em um valor de atributo , você acabaria com uma vulnerabilidade XSS.
James Roper

21
em que contexto é chalke cheesesempre usado em conjunto 0_o
d -_- b

2
@d -_- b ao comparar dois itens. exemplo. eles são tão diferentes como giz e queijo;)
Anurag

Respostas:


1067

EDIT: Esta resposta foi postada há muito tempo, e a htmlDecodefunção introduziu uma vulnerabilidade XSS. Foi modificado alterando o elemento temporário de a divpara textareareduzir a chance de XSS. Hoje em dia, porém, eu encorajo você a usar a API DOMParser, conforme sugerido em outra resposta .


Eu uso estas funções:

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

Basicamente, um elemento de área de texto é criado na memória, mas nunca é anexado ao documento.

Na htmlEncodefunção, defino o innerTextdo elemento e recupero o codificado innerHTML; na htmlDecodefunção, defino o innerHTMLvalor do elemento e o innerTexté recuperado.

Veja um exemplo em execução aqui .


95
Isso funciona na maioria dos cenários, mas essa implementação do htmlDecode eliminará qualquer espaço em branco extra. Portanto, para alguns valores de "input", insira! = HtmlDecode (htmlEncode (input)). Isso foi um problema para nós em alguns cenários. Por exemplo, se input = "<p> \ t Hi \ n There </p>", uma codificação / decodificação de ida e volta produzirá "<p> Hi There </p>". Na maioria das vezes isso é bom, mas às vezes não é. :)
pettys 19/03/10

7
Obrigado pela solução! Resolvi o problema de eliminação de espaço em branco extra, substituindo novas linhas com %% NL %% no valor do texto, depois chamei .html () para obter o valor codificado em HTML e substitui %% NL %% por <br /> ' s ... Não é à prova de balas, mas funcionou e é improvável que meus usuários digitem %% NL %%.
benno

1
O engraçado é que o CSS tem uma white-spacepropriedade, o que sugere como os espaços no conteúdo HTML devem ser processados. A presença da propriedade implica que "isto é pré-formatado, espaços e quebras de linha devem ser preservados". Isso interrompe a separação entre estilo e conteúdo, porque se você tentar reformatar o HTML para que seja "bonito" ou percorrê-lo através de um ciclo de codificação / decodificação como esse, as execuções de espaços / interrupções serão reduzidas e o codificador não terá maneira de saber se estava certo fazê-lo, porque não está ciente do white-space:pre-*;indicador em um arquivo CSS externo!
Triynko 30/08/11

2
Essa solução pode depender se a página está escrita como html ou xhtml, portanto, eu preferiria uma solução que não envolva o DOM.
Phil H

30
Embora tenha sido respondida dois anos depois, a resposta da @Anentropic abaixo é melhor em todos os aspectos.
chad

559

O truque do jQuery não codifica aspas e, no IE, reduz o espaço em branco.

Baseado no template de escape no Django, que eu acho que já é muito usado / testado, criei essa função que faz o que é necessário.

É sem dúvida mais simples (e possivelmente mais rápido) do que qualquer uma das soluções alternativas para o problema de remoção de espaços em branco - e codifica aspas, o que é essencial se você usar o resultado dentro de um valor de atributo, por exemplo.

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, '&');
}

Atualização 2013-06-17:
Na busca pela fuga mais rápida, encontrei esta implementação de um replaceAllmétodo:
http://dumpsite.com/forum/index.php?topic=4.msg29#msg29
(também referenciada aqui: mais rápida método para substituir todas as instâncias de um caractere em uma string )
Alguns resultados de desempenho aqui:
http://jsperf.com/htmlencoderegex/25

Ele fornece uma sequência de resultados idêntica às replacecadeias internas acima. Ficaria muito feliz se alguém pudesse explicar por que é mais rápido !?

Atualização 2015-03-04:
Acabei de perceber que o AngularJS está usando exatamente o método acima:
https://github.com/angular/angular.js/blob/v1.3.14/src/ngSanitize/sanitize.js#L435

Eles adicionam alguns refinamentos - eles parecem estar lidando com um problema Unicode obscuro , além de converter todos os caracteres não alfanuméricos em entidades. Fiquei com a impressão de que o último não era necessário, desde que você tenha um conjunto de caracteres UTF8 especificado para o seu documento.

Vou notar que (4 anos depois) o Django ainda não faz nenhuma dessas coisas, então não tenho certeza de quão importantes elas são:
https://github.com/django/django/blob/1.8b1/django/utils /html.py#L44

Atualização 06/06/2016:
Você também pode querer escapar da barra /. Isso não é necessário para a codificação HTML correta, no entanto, é recomendado pelo OWASP como uma medida de segurança anti-XSS. (obrigado a @JNF por sugerir isso nos comentários)

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

3
Você também pode usar em &apos;vez de&#39;
Ferruccio


5
Obrigado, nunca percebi que &apos;não é uma entidade HTML válida.
Ferruccio

10
Sem o /g, .replace()substituirá apenas a primeira partida.
ThinkingStiff

1
@ Tracker1 Não concordo, se a função receber uma entrada inválida, deverá gerar um erro. Se, em um caso de uso específico, você desejar manipular entradas inválidas dessa maneira, verifique o valor antes de chamar a função ou agrupe a chamada de função em uma tentativa / captura.
Anentropic

80

Aqui está uma versão que não é do jQuery que é consideravelmente mais rápida que a .html()versão do jQuery e a .replace()versão. Isso preserva todo o espaço em branco, mas, como a versão do jQuery, não manipula aspas.

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

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

Teste rápido

Demo: jsFiddle

Resultado:

resultado

Roteiro:

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
Isso levanta a questão: por que já não é uma função global em JS ?!
Seof

2
a .replace()versão não regex recentemente sugerida por @SEoF acaba sendo extremamente mais rápida: jsperf.com/htmlencoderegex/22
Anentropic

@ Anentropic Isso de fato ilumina rápido, mas não acho que esteja funcionando. Sem /g, .replace()está apenas fazendo a primeira partida.
ThinkingStiff

replace('a', 'b', 'g')replace(/a/g, 'b')
Curiosamente

1
nem eu :) Eu comecei apenas querendo aspas punho e eu acabei em uma busca de velocidade ...
Anentropic

32

Sei que é antiga, mas queria postar uma variação da resposta aceita que funcionará no IE sem remover linhas:

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

Boa resposta. Observe que, se o valor a codificar for undefinedou nullcom o jQuery 1.4.2, você poderá obter erros como:

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

OU

Uncaught TypeError: Object has no method 'html'

A solução é modificar a função para verificar um valor real:

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

8
jQuery('<div/>').text(value || '').html()
roufamatic 6/09/11

3
@roufamatic - Nice one-liner. Mas verificar se há um não vaziovalue com uma ifeconomia de criar um DIV on the fly e pegar seu valor. Isso pode ter muito mais desempenho se htmlEncodefor chamado muito E se for provável que valueele esteja vazio.
01

Oi ele não faz β to & beta você sabe por quê?
precisa

11

Para aqueles que preferem javascript simples, eis o método que usei com sucesso:

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

6

FWIW, a codificação não está sendo perdida. A codificação é usada pelo analisador de marcação (navegador) durante o carregamento da página. Depois que a fonte é lida e analisada e o navegador carrega o DOM na memória, a codificação é analisada no que representa. Assim, quando seu JS é executado para ler qualquer coisa na memória, o caractere recebido é o que a codificação representa.

Talvez eu esteja operando estritamente na semântica aqui, mas queria que você entendesse o propósito da codificação. A palavra "perdido" faz parecer que algo não está funcionando como deveria.


6

Mais rápido sem o Jquery. Você pode codificar todos os caracteres da sua string:

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

Ou apenas alveje os personagens principais com os quais se preocupar (&, inebreaks, <,>, "e ') 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

O protótipo foi incorporado à classe String . Portanto, se você estiver usando / planeja usar o Prototype, ele fará algo como:

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

9
Depois de analisar a solução da Prototype, isso é tudo o que está fazendo ... .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); Fácil o suficiente.
Steve Wortham

5
não deveria fazer algo com aspas também? Isso não é bom
Anentropic

@ Anentropic Não vejo por que precisaria fazer algo com aspas; pois as aspas não precisam ser escapadas, a menos que estejam dentro de um valor de atributo.
Andy

OK, depois de alguma reflexão, retiro esse comentário - se você estiver criando um pedaço de HTML, você gostaria de codificar cada parte dele, incluindo os valores dos atributos, então concordo com o Anentropic e não acho que a função Prototypejs seja suficiente em Aquele caso.
Andy

4

Aqui está uma solução javascript simples. Ele estende o objeto String com um método "HTMLEncode" que pode ser usado em um objeto sem parâmetro ou com um 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("æåøåæå"))

Eu criei uma essência "método HTMLEncode para javascript" .


3

Baseado na higienização angular ... (sintaxe do 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};

Embora eu realmente goste desta resposta e, na verdade, acho que seja uma boa abordagem, tenho uma dúvida: o operador bit a bit está if (value === null | value === undefined) return '';incorreto ou é um recurso? Se sim, por que usar esse e não o comum ||? Obrigado!!
Alejandro Vales

1
@AlejandroVales Tenho certeza de que foi um erro de digitação ... corrigido.
precisa

1
Bem, de qualquer forma, tenha em mente que o | levará a 0 ou 1, então na verdade funcionou ^^
Alejandro Vales

você não poderia simplesmente usar == null? undefinedé a única coisa com a qual ter equivalência null; portanto, dois triplos iguais não são necessários de qualquer maneira
Hashbrown

Isso definitivamente não é verdade. nulle 0ambos são falsos, sim, então você não pode simplesmente fazer isso !value, mas o objetivo ==é facilitar algumas coisas.0 == nullé falso. undefined == nullé verdade. você pode apenas fazervalue == null
Hashbrown

3

Tanto quanto eu sei, não há nenhum método HTML Encode / Decode em javascript.

No entanto, o que você pode fazer é usar JS para criar um elemento arbitrário, definir seu texto interno e, em seguida, lê-lo usando innerHTML.

Digamos que, com o jQuery, isso funcione:

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

Ou algo nesse sentido.


Acho o voto negativo um pouco divertido, considerando que essa resposta é quase idêntica à que tem mais de 870 votos positivos e foi postada um pouco depois deste.
Ken Egozi 03/02

2

Você não precisa escapar / codificar valores para transferi-los de um campo de entrada para outro.

<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 não vai inserir HTML bruto ou qualquer coisa; apenas informa ao DOM para definir a valuepropriedade (ou atributo; não tenho certeza). De qualquer forma, o DOM lida com qualquer problema de codificação para você. A menos que você esteja fazendo algo estranho como usardocument.write ou eval, a codificação HTML será efetivamente transparente.

Se você está falando sobre gerar uma nova caixa de texto para manter o resultado ... ainda é fácil. Apenas passe a parte estática do HTML para jQuery e defina o restante das propriedades / atributos no objeto que ele retornar para você.

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

2

Eu tive um problema semelhante e resolvi-o usando a função encodeURIComponentdo JavaScript ( documentação )

Por exemplo, no seu caso, se você usar:

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

e

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

você receberá chalk%20%26%20cheese. Mesmo espaços são mantidos.

No meu caso, tive que codificar uma barra invertida e esse código funciona perfeitamente

encodeURIComponent('name/surname')

e eu tenho name%2Fsurname


2

Aqui está um pouco que emula a Server.HTMLEncodefunção do ASP da Microsoft, escrita em 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;
}

O resultado não codifica apóstrofes, mas codifica os outros especiais em HTML e qualquer caractere fora do intervalo 0x20-0x7e.



1

Se você deseja usar o jQuery. Eu achei isto:

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

(parte do plugin jquery.string oferecido pelo jQuery SDK)

O problema com o Prototype, acredito, é que ele estende objetos de base em JavaScript e será incompatível com qualquer jQuery que você possa ter usado. Obviamente, se você já estiver usando o Prototype e não o jQuery, não será um problema.

EDIT: Também existe isso, que é uma porta dos utilitários de string do 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
    };
})();

Isto é do código fonte 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>

Saída: &lt;script&gt;alert(&quot;I hack your site&quot;)&lt;/script&gt;

.htmlEncode () estará acessível em todas as strings, uma vez definidas.


1

Html Codifica o valor fornecido

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


0

Escolhendo o que escapeHTML()está fazendo no prototype.js

Adicionar este script ajuda você a escaparHTML:

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

agora você pode chamar o método escapeHTML em cadeias de caracteres em seu script, como:

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

Espero que ajude quem procura uma solução simples sem precisar incluir todo o prototype.js


0

Usando algumas das outras respostas aqui, criei uma versão que substitui todos os caracteres pertinentes em uma passagem, independentemente do número de caracteres codificados distintos (apenas uma chamada para replace()), para que seja mais rápido para seqüências maiores.

Ele não depende da API do DOM para existir ou de outras 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); };
})();

Depois de executá-lo uma vez, agora você pode ligar

encodeHTML('<>&"\'')

Para obter &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);

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.