Méthode la plus rapide pour échapper aux balises HTML en tant qu'entités HTML?

98

J'écris une extension Chrome qui implique une grande partie du travail suivant: nettoyer les chaînes qui peuvent contenir des balises HTML, en convertissant <, >et &en &lt;, &gt;et &amp;, respectivement.

(En d'autres termes, la même chose que PHP htmlspecialchars(str, ENT_NOQUOTES)- je ne pense pas qu'il soit vraiment nécessaire de convertir les caractères entre guillemets.)

C'est la fonction la plus rapide que j'ai trouvée jusqu'à présent:

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

Mais il y a encore un gros décalage lorsque je dois exécuter quelques milliers de cordes en une seule fois.

Quelqu'un peut-il améliorer cela? C'est surtout pour les chaînes de 10 à 150 caractères, si cela fait une différence.

(Une idée que j'avais était de ne pas prendre la peine d'encoder le signe supérieur à - y aurait-il un réel danger avec cela?)

callum
la source
2
Pourquoi? Dans la plupart des cas où vous voulez faire cela, vous voulez insérer les données dans le DOM, auquel cas vous devriez oublier de les échapper et simplement en créer un textNode.
Quentin
1
@David Dorward: il voulait peut-être assainir les données POST, et le serveur ne fait pas correctement l'aller-retour des données.
Lie Ryan
4
@Lie - si c'est le cas, alors la solution est "Pour l'amour de Pete, réparez le serveur car vous avez un gros trou XSS"
Quentin
2
@David Dorward: il est possible que dans le cas où il n'ait pas le contrôle sur le serveur. J'ai été dans une telle situation récemment où j'écrivais un script greasemonkey pour contourner quelques choses que je n'aime pas sur le site Web de mon université; J'ai dû faire un POST sur un serveur sur lequel je n'ai pas de contrôle et désinfecter les données POST en utilisant javascript (puisque les données brutes proviennent d'une zone de texte riche, et donc des tas de balises html qui ne font pas d'aller-retour sur le serveur) . L'administrateur Web ignorait ma demande de réparation du site Web, je n'avais donc pas d'autre choix.
Lie Ryan
1
J'ai un cas d'utilisation où j'ai besoin d'afficher un message d'erreur dans un div. Le message d'erreur peut contenir du HTML et des retours à la ligne. Je veux échapper au HTML et remplacer les nouvelles lignes par <br>. Ensuite, mettez le résultat dans un div pour l'affichage.
mozey le

Réponses:

83

Vous pouvez essayer de passer une fonction de rappel pour effectuer le remplacement:

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

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

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

Voici un test de performance: http://jsperf.com/encode-html-entities à comparer avec l'appel de la replacefonction à plusieurs reprises, et en utilisant la méthode DOM proposée par Dmitrij.

Votre chemin semble être plus rapide ...

Pourquoi en avez-vous besoin?

Martijn
la source
2
Il n'y a pas besoin de s'échapper >.
6
En fait, si vous mettez la valeur échappée dans l'attribut d'un élément html, vous devez échapper le symbole>. Sinon, cela briserait la balise de cet élément html.
Zlatin Zlatev
1
Dans le texte normal, les caractères d'échappement sont rares. Il est préférable d'appeler le remplacement uniquement en cas de besoin, si vous vous souciez de la vitesse maximale:if (/[<>&"]/.test(str) { ... }
Vitaly
3
@callum: Non. Je ne suis pas intéressé par l'énumération des cas dans lesquels je pense que "quelque chose pourrait mal tourner" (notamment parce que ce sont les cas inattendus / oubliés qui vont vous blesser, et quand vous vous y attendez le moins). Je suis intéressé par le codage selon les normes (donc les cas inattendus / oubliés ne peuvent pas vous blesser par définition ). Je ne peux pas souligner à quel point c'est important. >est un caractère spécial en HTML, alors échappez-le. Aussi simple que cela. :)
Courses de légèreté en orbite
4
@LightnessRacesinOrbit C'est pertinent car la question est de savoir quelle est la méthode la plus rapide possible. S'il est possible de sauter le >remplacement, cela le rendrait plus rapide.
callum
104

Voici une façon de procéder:

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

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

Voici une démo.

Web_Designer
la source
Refonte de la démo. Voici une version plein écran: jsfiddle.net/Daniel_Hug/qPUEX/show/light
Web_Designer
13
Je ne sais pas comment / quoi / pourquoi - mais c'est du génie.
rob_james
4
On dirait qu'il exploite le code existant de l'élément TextArea pour échapper du texte littéral. Très sympa, je pense que ce petit truc va trouver une autre maison.
Ajax le
3
@jazkat Je n'utilise pas cette fonction. La variable d'échappement que j'utilise, je me définis moi-même dans l'exemple.
Web_Designer
2
mais cela perd-il un espace blanc, etc.
Andrew
31

La méthode de Martijn comme fonction prototype:

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
la source
12
Ajoutez à Stringcela, cela devrait être escapeHtml car ce n'est pas un échappement pour une chaîne en général. C'est String.escapeHtmlexact, mais String.escapesoulève la question, "échapper pour quoi?"
Lawrence Dol
3
Ouais bonne idée. Je me suis éloigné de l'extension du prototype ces jours-ci pour éviter les conflits.
Aram Kocharyan
1
Si votre navigateur prend en charge Symbol, vous pouvez l'utiliser à la place pour éviter de polluer l'espace de noms de clé de chaîne. var escape = new Symbol ("escape"); String.prototype [escape] = function () {...}; "texte" [échapper] ();
Ajax le
12

Une solution encore plus rapide / plus courte est:

escaped = new Option(html).innerHTML

Ceci est lié à un vestige étrange de JavaScript dans lequel l'élément Option conserve un constructeur qui fait ce genre d'échappatoire automatiquement.

Crédit à https://github.com/jasonmoo/t.js/blob/master/t.js

Todd
la source
1
Neat one-liner mais la méthode la plus lente après regex. En outre, le texte ici peut avoir des espaces vides, selon la spécification
ShortFuse
Notez que le lien «méthode la plus lente» de @ ShortFuse rend mon système à court de RAM (avec ~ 6 Go d'espace libre) et Firefox semble arrêter d'allouer juste avant qu'il n'y ait plus de mémoire, donc au lieu de tuer le processus incriminé, linux restera là et vous laissera faire une mise hors tension dure.
Luc
11

Le code source AngularJS a également une version à l'intérieur 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
la source
1
Wow, ce regex non alphanum est intense. Je ne pense pas que le | dans l'expression est cependant nécessaire.
Ajax le
9

Script tout-en-un:

// 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
la source
Je n'ai pas contre-voté, mais tout remplacement de style regex échouera à encoder unicode ... Donc, quiconque utilise une langue étrangère sera déçu. L'astuce <textarea> mentionnée ci-dessus est vraiment cool et gère tout rapidement et en toute sécurité.
Ajax
Le regex fonctionne très bien pour moi avec un certain nombre de caractères Unicode non latins. Je ne m'attendrais à rien d'autre. Comment pensez-vous que cela ne fonctionnerait pas? Envisagez-vous des pages de code à un octet qui nécessitent des entités HTML? C'est à cela que servent les 3e et 4e fonctions, et explicitement pas la 1ère et la seconde. J'aime la différenciation.
ygoe le
@LonelyPixel Je ne pense pas qu'il verra votre commentaire si vous ne le mentionnez pas ("Un seul utilisateur supplémentaire peut être notifié; le propriétaire du message sera toujours averti")
baptx
Je ne savais pas du tout l'existence de notifications ciblées. @Ajax s'il vous plaît voir mon commentaire ci-dessus.
ygoe
@LonelyPixel Je vois maintenant. Pour une raison quelconque, je ne pensais pas qu'il y avait un remplacement de style textarea dans cette réponse. Je pensais, en effet, à de grandes valeurs Unicode à double codet, comme le mandarin. Je veux dire, il serait possible de faire une regex assez intelligente, mais quand vous regardez les raccourcis que les fournisseurs de navigateurs peuvent prendre, je me sentirais plutôt bien parier que textarea sera beaucoup plus rapide (qu'une regex complètement compétente). Quelqu'un a-t-il publié un benchmark sur cette réponse? J'ai juré d'en avoir vu un.
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
la source
1

Je ne suis pas tout à fait sûr de la vitesse, mais si vous recherchez la simplicité, je suggérerais d'utiliser la fonction d' échappement lodash / underscore .

gilmatic
la source
0

La méthode de Martijn en tant que fonction unique avec gestion de la marque " ( utilisation 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
la source
0

Je vais ajouter XMLSerializerà la pile. Il fournit le résultat le plus rapide sans utiliser de mise en cache d'objets (ni sur le sérialiseur, ni sur le nœud Texte).

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

L'avantage supplémentaire est qu'il prend en charge les attributs qui sont sérialisés différemment des nœuds de texte:

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

Vous pouvez voir ce qu'il remplace réellement en vérifiant la spécification, à la fois pour les nœuds de texte et pour les valeurs d'attribut . La documentation complète a plus de types de nœuds, mais le concept est le même.

En ce qui concerne les performances, c'est le plus rapide lorsqu'il n'est pas mis en cache. Lorsque vous autorisez la mise en cache, l'appel innerHTMLà un HTMLElement avec un nœud Text enfant est le plus rapide. Regex serait le plus lent (comme le prouvent d'autres commentaires). Bien sûr, XMLSerializer pourrait être plus rapide sur d'autres navigateurs, mais dans mes tests (limités), a innerHTMLest le plus rapide.


Ligne unique la plus rapide:

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

Le plus rapide avec la mise en cache:

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

Mèche courte
la source
-3

Un peu tard pour le spectacle, mais quel est le problème avec l'utilisation d' encodeURIComponent () et decodeURIComponent () ?

suncat100
la source
1
Ceux-ci font quelque chose de complètement indépendant
callum
1
Peut-être le plus gros abus du mot «complètement» que j'aie jamais entendu. Par exemple, en ce qui concerne la question du sujet principal, il pourrait être utilisé pour décoder une chaîne html (évidemment pour une raison de stockage), indépendamment des balises html, puis la coder facilement à nouveau en html quand et si nécessaire.
suncat100