Décoder & amp; retour à & en JavaScript

230

J'ai des cordes comme

var str = 'One & two & three';

rendu en HTML par le serveur web. J'ai besoin de transformer ces cordes en

'One & two & three'

Actuellement, c'est ce que je fais (avec l'aide de jQuery):

$(document.createElement('div')).html('{{ driver.person.name }}').text()

Cependant, j'ai le sentiment troublant de me tromper. j'ai essayé

unescape("&")

mais cela ne semble pas fonctionner, ni decodeURI / decodeURIComponent.

Y a-t-il d'autres façons plus natives et élégantes de le faire?

Art
la source
L'énorme fonction incluse dans cet article semble fonctionner correctement : blogs.msdn.com/b/aoakley/archive/2003/11/12/49645.aspx Je ne pense pas que ce soit la solution la plus intelligente, mais elle fonctionne.
Matias
1
Comme les chaînes contenant des entités HTML sont différentes des chaînes codéesescape d ou URI , ces fonctions ne fonctionneront pas.
Marcel Korpel
1
@Matias note que de nouvelles entités nommées ont été ajoutées au HTML (par exemple via la spécification HTML 5) depuis que cette fonction a été créée en 2003 - par exemple, elle ne reconnaît pas 𝕫. C'est un problème avec une spécification en évolution; en tant que tel, vous devez choisir un outil en cours de maintenance pour le résoudre.
Mark Amery
1
@MarkAmery oui, je suis totalement d'accord! C'est une belle expérience de revenir sur ces questions après quelques années, merci!
Matias

Réponses:

105

Une option plus moderne pour interpréter HTML (texte et autres) à partir de JavaScript est le support HTML dans l' DOMParserAPI ( voir ici dans MDN ). Cela vous permet d'utiliser l'analyseur HTML natif du navigateur pour convertir une chaîne en un document HTML. Il est pris en charge dans les nouvelles versions de tous les principaux navigateurs depuis fin 2014.

Si nous voulons simplement décoder du contenu textuel, nous pouvons le mettre comme seul contenu dans un corps de document, analyser le document et retirer le son .body.textContent.

var encodedStr = 'hello & world';

var parser = new DOMParser;
var dom = parser.parseFromString(
    '<!doctype html><body>' + encodedStr,
    'text/html');
var decodedString = dom.body.textContent;

console.log(decodedString);

Nous pouvons voir dans le projet de spécificationDOMParser que JavaScript n'est pas activé pour le document analysé, nous pouvons donc effectuer cette conversion de texte sans problèmes de sécurité.

La parseFromString(str, type)méthode doit exécuter ces étapes, selon le type :

  • "text/html"

    Analyser str avec un HTML parseret renvoyer le nouveau Document.

    L'indicateur de script doit être défini sur "désactivé".

    REMARQUE

    scriptles éléments sont marqués comme non exécutables et le contenu de noscriptest analysé en tant que balisage.

Cela dépasse le cadre de cette question, mais veuillez noter que si vous prenez les nœuds DOM analysés eux-mêmes (pas seulement leur contenu texte) et les déplacez vers le document en direct DOM, il est possible que leur script soit réactivé, et il pourrait y avoir être des préoccupations de sécurité. Je n'ai pas fait de recherche, alors soyez prudent.

Jeremy Banks
la source
5
une alternative pour NodeJs?
coderInrRain
285

Avez-vous besoin de décoder toutes les entités HTML encodées ou juste &amp;lui - même?

Si vous avez seulement besoin de gérer, &amp;vous pouvez le faire:

var decoded = encoded.replace(/&amp;/g, '&');

Si vous devez décoder toutes les entités HTML, vous pouvez le faire sans jQuery:

var elem = document.createElement('textarea');
elem.innerHTML = encoded;
var decoded = elem.value;

Veuillez prendre note des commentaires de Mark ci-dessous qui mettent en évidence les failles de sécurité dans une version antérieure de cette réponse et recommandez d'utiliser textareaplutôt que divd'atténuer les vulnérabilités XSS potentielles. Ces vulnérabilités existent que vous utilisiez jQuery ou JavaScript simple.

LukeH
la source
16
Il faut se méfier! C'est potentiellement dangereux. Si encoded='<img src="bla" onerror="alert(1)">'alors l'extrait ci-dessus affichera une alerte. Cela signifie que si votre texte encodé provient d'une entrée utilisateur, le décoder avec cet extrait peut présenter une vulnérabilité XSS.
Mark Amery
@MarkAmery Je ne suis pas un expert en sécurité, mais il semble que si vous définissez immédiatement le div sur nullaprès avoir reçu le texte, l'alerte dans l'img n'est pas déclenchée - jsfiddle.net/Mottie/gaBeb/128
Mottie
4
@Mottie notez bien le navigateur qui a fonctionné pour vous, mais il alert(1)se déclenche toujours pour moi sur Chrome sur OS X. Si vous voulez une variante sûre de ce hack, essayez d' utiliser untextarea .
Mark Amery
+1 pour la regexp simple remplace l'alternative pour un seul type d'entité html. Utilisez-le si vous vous attendez à ce que des données html soient interpolées, par exemple, d'une application de flacon python vers un modèle.
OzzyTheGiant
Comment faire cela sur le serveur Node?
Mohammad Kermani
44

Matthias Bynens a une bibliothèque pour cela: https://github.com/mathiasbynens/he

Exemple:

console.log(
    he.decode("J&#246;rg &amp J&#xFC;rgen rocked to &amp; fro ")
);
// Logs "Jörg & Jürgen rocked to & fro"

Je suggère de le préférer aux hacks impliquant de définir le contenu HTML d'un élément, puis de relire son contenu textuel. De telles approches peuvent fonctionner, mais sont trompeusement dangereuses et présentent des opportunités XSS si elles sont utilisées sur des entrées utilisateur non fiables.

Si vous ne pouvez vraiment pas supporter de charger dans une bibliothèque, vous pouvez utiliser le textareahack décrit dans cette réponse à une question presque en double, qui, contrairement à diverses approches similaires qui ont été suggérées, n'a pas de failles de sécurité à ma connaissance:

function decodeEntities(encodedString) {
    var textArea = document.createElement('textarea');
    textArea.innerHTML = encodedString;
    return textArea.value;
}

console.log(decodeEntities('1 &amp; 2')); // '1 & 2'

Mais prenez note des problèmes de sécurité, affectant des approches similaires à celle-ci, que j'énumère dans la réponse liée! Cette approche est un hack, et les modifications futures du contenu autorisé d'un textarea(ou des bogues dans les navigateurs en particulier) pourraient conduire à un code qui repose sur un soudain trou XSS un jour.

Mark Amery
la source
La bibliothèque de Matthias Bynens heest absolument géniale! Merci beaucoup pour la recommandation!
Pedro A
23
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
    };
})();

Ceci provient du code source ExtJS.

WaiKit Kung
la source
4
-1; cela ne parvient pas à gérer la grande majorité des entités nommées. Par exemple, htmlEnDecode.htmlDecode('&euro;')devrait revenir '€', mais revient à la place '&euro;'.
Mark Amery
17

element.innerText fait aussi l'affaire.

avg_joe
la source
15

Vous pouvez utiliser la fonction unescape / escape de Lodash https://lodash.com/docs/4.17.5#unescape

import unescape from 'lodash/unescape';

const str = unescape('fred, barney, &amp; pebbles');

str deviendra 'fred, barney, & pebbles'

Je suis l
la source
1
il vaut probablement mieux "importer _unescape depuis 'lodash / unescape';" donc il n'entre pas en conflit avec la fonction javascript obsolète du même nom: unescape
Rick Penabella
14

Dans le cas où vous le recherchez, comme moi - en attendant, il existe une méthode JQuery agréable et sûre.

https://api.jquery.com/jquery.parsehtml/

Vous pouvez f.ex. tapez ceci dans votre console:

var x = "test &amp;";
> undefined
$.parseHTML(x)[0].textContent
> "test &"

Donc $ .parseHTML (x) retourne un tableau, et si vous avez du balisage HTML dans votre texte, le tableau.length sera supérieur à 1.

cslotty
la source
A parfaitement fonctionné pour moi, c'était exactement ce que je cherchais, merci.
Jonathan Nielsen
1
Si xa une valeur de <script>alert('hello');</script>ce qui précède plantera. Dans jQuery actuel, il n'essaiera pas d'exécuter le script, mais [0]il donnera undefineddonc l'appel à textContentéchouera et votre script s'arrêtera là. $('<div />').html(x).text();semble plus sûr - via gist.github.com/jmblog/3222899
Andrew Hodgkinson
@AndrewHodgkinson oui, mais la question était "Décoder et revenir à & en JavaScript" - vous devriez donc d'abord tester le contenu de x ou vous assurer de ne l'utiliser que dans les cas appropriés.
cslotty
Je ne vois pas vraiment comment cela se produit. Le code ci-dessus fonctionne dans tous les cas. Et comment exactement "vous assureriez-vous" que la valeur de x devait être corrigée? Et si l'exemple de script ci-dessus alertait '& amp;' de sorte qu'il avait vraiment besoin d'une correction? Nous n'avons aucune idée d'où viennent les chaînes de l'OP, donc une entrée malveillante doit être considérée.
Andrew Hodgkinson
@AndrewHodgkinson J'aime votre considération, mais ce n'est pas la question ici. N'hésitez pas à répondre à cette question. Je suppose que vous pouvez supprimer les balises de script, f.ex.
cslotty
8

jQuery va encoder et décoder pour vous. Cependant, vous devez utiliser une balise textarea, pas une div.

var str1 = 'One & two & three';
var str2 = "One &amp; two &amp; three";
  
$(document).ready(function() {
   $("#encoded").text(htmlEncode(str1)); 
   $("#decoded").text(htmlDecode(str2));
});

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

function htmlEncode(value) {
  return $('<textarea/>').text(value).html();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

<div id="encoded"></div>
<div id="decoded"></div>

Jason Williams
la source
2
-1 car il y a un trou de sécurité (surprenant) pour les anciennes versions de jQuery, dont certaines ont probablement encore une base d'utilisateurs importante - ces versions détecteront et évalueront explicitement les scripts dans le HTML transmis à .html(). Ainsi, même l'utilisation d'un textareane suffit pas pour assurer la sécurité ici; Je suggère de ne pas utiliser jQuery pour cette tâche et d'écrire du code équivalent avec l'API DOM ordinaire . (Oui, ce vieux comportement de jQuery est fou et horrible.)
Mark Amery
Merci d'avoir fait remarquer cela. Cependant, la question n'inclut pas l'obligation de vérifier l'injection de script. La question concerne spécifiquement le HTML rendu par le serveur Web. Le contenu HTML enregistré sur un serveur Web devrait probablement être validé pour l'injection de script avant d'être enregistré.
Jason Williams
4

Créez d'abord un <span id="decodeIt" style="display:none;"></span> endroit dans le corps

Ensuite, affectez la chaîne à décoder comme innerHTML à ceci:

document.getElementById("decodeIt").innerHTML=stringtodecode

Finalement,

stringtodecode=document.getElementById("decodeIt").innerText

Voici le code global:

var stringtodecode="<B>Hello</B> world<br>";
document.getElementById("decodeIt").innerHTML=stringtodecode;
stringtodecode=document.getElementById("decodeIt").innerText
Infoglaze.com
la source
1
-1; c'est dangereusement dangereux à utiliser sur des entrées non fiables. Par exemple, considérez ce qui se passe si stringtodecodecontient quelque chose comme <script>alert(1)</script>.
Mark Amery
2

une solution javascript qui attrape les plus courantes:

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

c'est l'inverse de https://stackoverflow.com/a/4835406/2738039

Peter Brandt
la source
Si vous utilisez map[c] || ''des outils non reconnus, ils ne seront pas affichés commeundefined
Eldelshell
Couverture très limitée; -1.
Mark Amery
2
+1, plus estunescapeHtml(str){ var map = {amp: '&', lt: '<', le: '≤', gt: '>', ge: '≥', quot: '"', '#039': "'"} return str.replace(/&([^;]+);/g, (m, c) => map[c]|| '') }
Trần Quốc Hoài nouveau
Couverture manuelle. Non recommandé.
Sergio A.
2

Pour les gars d'une ligne:

const htmlDecode = innerHTML => Object.assign(document.createElement('textarea'), {innerHTML}).value;

console.log(htmlDecode('Complicated - Dimitri Vegas &amp; Like Mike'));
Ninh Pham
la source
2

La question ne précise pas l'origine de xmais il est logique de se défendre, si nous le pouvons, contre les entrées malveillantes (ou tout simplement inattendues, de notre propre application). Par exemple, supposons xque la valeur est &amp; <script>alert('hello');</script>. Un moyen sûr et simple de gérer cela dans jQuery est:

var x    = "&amp; <script>alert('hello');</script>";
var safe = $('<div />').html(x).text();

// => "& alert('hello');"

Trouvé via https://gist.github.com/jmblog/3222899 . Je ne vois pas beaucoup de raisons d'éviter d'utiliser cette solution étant donné qu'elle est au moins aussi courte, sinon plus courte que certaines alternatives et offre une défense contre XSS.

(J'ai initialement posté cela en tant que commentaire, mais je l'ajoute comme réponse, car un commentaire ultérieur dans le même fil m'a demandé de le faire).

Andrew Hodgkinson
la source
1

J'ai tout essayé pour supprimer et d'un tableau JSON. Aucun des exemples ci-dessus, mais https://stackoverflow.com/users/2030321/chris a donné une excellente solution qui m'a amené à résoudre mon problème.

var stringtodecode="<B>Hello</B> world<br>";
document.getElementById("decodeIt").innerHTML=stringtodecode;
stringtodecode=document.getElementById("decodeIt").innerText

Je ne l'ai pas utilisé, car je ne comprenais pas comment l'insérer dans une fenêtre modale qui tirait des données JSON dans un tableau, mais j'ai essayé cela en fonction de l'exemple, et cela a fonctionné:

var modal = document.getElementById('demodal');
$('#ampersandcontent').text(replaceAll(data[0],"&amp;", "&"));

Je l'aime parce que c'était simple et cela fonctionne, mais je ne sais pas pourquoi il n'est pas largement utilisé. Recherche hi & low pour trouver une solution simple. Je continue de chercher à comprendre la syntaxe et s'il y a un risque à l'utiliser. Je n'ai encore rien trouvé.

Digexart
la source
Votre première proposition est juste un peu délicate, mais elle fonctionne bien sans trop d'effort. Le second, en revanche, n'utilise que la force brute pour décoder les caractères; cela signifie que cela pourrait prendre beaucoup d'efforts et de temps pour accomplir une fonction de décodage complète. C'est pourquoi personne n'utilise cette méthode pour résoudre le problème d'OP.
Sergio A.