Schnellste Methode, um HTML-Tags als HTML-Entitäten zu umgehen?

98

Ich schreibe eine Chrome - Erweiterung , die eine beinhaltet dabei viel von dem folgenden Job: Hygienisierung Zeichenfolge , die möglicherweise HTML - Tags enthalten, durch die Umwandlung <, >und &zu &lt;, &gt;und &amp;sind.

(Mit anderen Worten, genau wie bei PHP htmlspecialchars(str, ENT_NOQUOTES)- ich glaube nicht, dass es wirklich notwendig ist, doppelte Anführungszeichen zu konvertieren.)

Dies ist die schnellste Funktion, die ich bisher gefunden habe:

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

Aber es gibt immer noch eine große Verzögerung, wenn ich ein paar tausend Saiten auf einmal durchlaufen muss.

Kann jemand das verbessern? Es ist meistens für Zeichenfolgen zwischen 10 und 150 Zeichen, wenn das einen Unterschied macht.

(Eine Idee, die ich hatte, war, sich nicht die Mühe zu machen, das Größer-als-Zeichen zu verschlüsseln - würde das eine echte Gefahr darstellen?)

Callum
quelle
2
Warum? In den meisten Fällen, in denen Sie dies tun möchten, möchten Sie die Daten in das DOM einfügen. In diesem Fall sollten Sie vergessen, sie zu maskieren, und einfach einen Textknoten daraus erstellen.
Quentin
1
@ David Dorward: Vielleicht wollte er POST-Daten bereinigen, und der Server setzt die Daten nicht korrekt um.
Lie Ryan
4
@Lie - wenn ja, dann ist die Lösung "Um Pete's willen, reparieren Sie den Server, da Sie ein großes XSS-Loch haben"
Quentin
2
@ David Dorward: Es ist möglich, dass er keine Kontrolle über den Server hat. Ich war kürzlich in einer solchen Situation, in der ich ein Greasemonkey-Skript geschrieben habe, um ein paar Dinge zu umgehen, die ich auf der Website meiner Universität nicht mag. Ich musste einen POST auf einem Server durchführen, für den ich keine Kontrolle habe, und POST-Daten mit Javascript bereinigen (da die Rohdaten aus einem umfangreichen Textfeld stammen und auch jede Menge HTML-Tags vorhanden sind, die keinen Roundtrip auf dem Server ausführen). . Der Webadministrator ignorierte meine Aufforderung, die Website zu reparieren, sodass ich keine andere Wahl hatte.
Lie Ryan
1
Ich habe einen Anwendungsfall, in dem ich eine Fehlermeldung in einem div anzeigen muss. Die Fehlermeldung kann HTML und Zeilenumbrüche enthalten. Ich möchte dem HTML-Code entkommen und die Zeilenumbrüche durch <br> ersetzen. Setzen Sie dann das Ergebnis zur Anzeige in ein div.
Mozey

Antworten:

82

Sie können versuchen, eine Rückruffunktion zu übergeben, um den Austausch durchzuführen:

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

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

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

Hier ist ein Leistungstest: http://jsperf.com/encode-html-entities zum Vergleich mit dem replacewiederholten Aufrufen der Funktion und der Verwendung der von Dmitrij vorgeschlagenen DOM-Methode.

Dein Weg scheint schneller zu sein ...

Warum brauchst du es aber?

Martijn
quelle
2
Es besteht keine Notwendigkeit zu entkommen >.
6
Wenn Sie den maskierten Wert in das Attribut eines HTML-Elements einfügen, müssen Sie das Symbol> maskieren. Andernfalls würde das Tag für dieses HTML-Element beschädigt.
Zlatin Zlatev
1
Im normalen Text sind maskierte Zeichen selten. Es ist besser, Ersatz nur bei Bedarf anzurufen, wenn Sie sich für maximale Geschwindigkeit interessieren:if (/[<>&"]/.test(str) { ... }
Vitaly
3
@callum: Nein. Ich bin nicht daran interessiert, Fälle aufzuzählen, in denen ich denke, dass "etwas schief gehen könnte" (nicht zuletzt, weil es die unerwarteten / vergessenen Fälle sind, die dich verletzen werden, und wenn du es am wenigsten erwartest). Ich bin daran interessiert, nach Standards zu codieren (daher können unerwartete / vergessene Fälle Sie per Definition nicht verletzen ). Ich kann nicht betonen, wie wichtig das ist. >ist ein Sonderzeichen in HTML, also entkommen Sie ihm. So einfach ist das. :)
Leichtigkeitsrennen im Orbit
4
@LightnessRacesinOrbit Es ist relevant, weil die Frage ist, was die schnellstmögliche Methode ist. Wenn es möglich ist, den >Austausch zu überspringen , würde dies ihn schneller machen.
Callum
101

Hier ist eine Möglichkeit, wie Sie dies tun können:

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

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

Hier ist eine Demo.

Webdesigner
quelle
Demo neu gestaltet. Hier ist eine Vollbildversion: jsfiddle.net/Daniel_Hug/qPUEX/show/light
Web_Designer
13
Ich weiß nicht wie / was / warum - aber das ist genial.
Rob_James
3
Sieht so aus, als würde der vorhandene Code des TextArea-Elements genutzt, um wörtlichen Text zu maskieren. Sehr schön, ich denke, dieser kleine Trick wird ein anderes Zuhause finden.
Ajax
3
@jazkat Ich benutze diese Funktion nicht. Die Escape-Variable, die ich benutze, definiere ich mir im Beispiel.
Web_Designer
1
Aber verliert dies Leerraum usw.
Andrew
31

Martijns Methode als Prototypfunktion:

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
quelle
12
Add to Stringlike this sollte es EscapeHtml sein, da es im Allgemeinen kein Escape für einen String ist. Das ist String.escapeHtmlrichtig, String.escapewirft aber die Frage auf: "Flucht für was?"
Lawrence Dol
3
Ja, gute Idee. Ich habe mich heutzutage von der Erweiterung des Prototyps entfernt, um Konflikte zu vermeiden.
Aram Kocharyan
1
Wenn Ihr Browser Symbol unterstützt, können Sie dies stattdessen verwenden, um eine Verschmutzung des String-Key-Namespace zu vermeiden. var Escape = neues Symbol ("Escape"); String.prototype [Escape] = function () {...}; "text" [Escape] ();
Ajax
11

Der AngularJS-Quellcode enthält auch eine Version in 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
quelle
1
Wow, dieser Nicht-Alphanum-Regex ist intensiv. Ich glaube nicht, dass die | im Ausdruck wird allerdings benötigt.
Ajax
11

Eine noch schnellere / kürzere Lösung ist:

escaped = new Option(html).innerHTML

Dies hängt mit einem seltsamen Überbleibsel von JavaScript zusammen, bei dem das Option-Element einen Konstruktor behält, der diese Art der automatischen Escape-Funktion ausführt.

Gutschrift an https://github.com/jasonmoo/t.js/blob/master/t.js

Todd
quelle
Ordentlicher Einzeiler, aber die langsamste Methode nach Regex. Außerdem kann der Text hier Leerzeichen entfernt werden, gemäß der Spezifikation
ShortFuse
Beachten Sie, dass @ ShortFuses Link "langsamste Methode" dazu führt, dass meinem System der Arbeitsspeicher ausgeht (mit ~ 6 GB frei) und Firefox die Zuweisung zu beenden scheint, bevor der Arbeitsspeicher knapp wird. Anstatt den fehlerhaften Prozess zu beenden, wird Linux dort sitzen und Sie dies tun lassen eine harte Abschaltung.
Luc
9

All-in-One-Skript:

// 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

Taufe
quelle
Ich habe nicht abgelehnt, aber alle Regex-Stil-Ersetzungen können Unicode nicht codieren ... Jeder, der eine Fremdsprache verwendet, wird enttäuscht sein. Der oben erwähnte <textarea> Trick ist wirklich cool und erledigt alles schnell und sicher.
Ajax
Die Regex funktioniert gut für mich mit einer Reihe von nicht-lateinischen Unicode-Zeichen. Ich würde nichts anderes erwarten. Wie denkst du, würde das nicht funktionieren? Denken Sie an Einzelbyte-Codepages, für die HTML-Entitäten erforderlich sind? Dafür sind die 3. und 4. Funktion gedacht und ausdrücklich nicht die 1. und 2. Funktion. Ich mag die Differenzierung.
Ygoe
@LonelyPixel Ich glaube nicht, dass er Ihren Kommentar sehen wird, wenn Sie ihn nicht erwähnen ("Nur ein weiterer Benutzer kann benachrichtigt werden; der
Postbesitzer
Ich wusste nicht, dass es überhaupt gezielte Benachrichtigungen gibt. @ Ajax bitte siehe meinen Kommentar oben.
Ygoe
@LonelyPixel sehe ich jetzt. Aus irgendeinem Grund dachte ich nicht, dass diese Antwort einen Ersatz für einen Textarea-Stil enthält. Ich dachte tatsächlich an große Unicode-Werte mit doppeltem Codepunkt wie Mandarin. Ich meine, es wäre möglich, einen regulären Ausdruck intelligent genug zu machen, aber wenn Sie sich die Verknüpfungen ansehen, die Browser-Anbieter verwenden können, würde ich ziemlich gut darauf wetten, dass der Textbereich viel schneller sein wird (als ein vollständig kompetenter regulärer Ausdruck). Hat jemand einen Benchmark für diese Antwort veröffentlicht? Ich schwor, ich hätte einen gesehen.
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
quelle
1

Bei der Geschwindigkeit bin ich mir nicht ganz sicher, aber wenn Sie nach Einfachheit suchen, würde ich die Verwendung der Lodash / Underscore- Escape- Funktion empfehlen .

gilmatisch
quelle
0

Martijns Methode als Einzelfunktion mit Handhabung " mark " ( in Javascript ):

function escapeHTML(html) {
    var fn=function(tag) {
        var charsToReplace = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&#34;'
        };
        return charsToReplace[tag] || tag;
    }
    return html.replace(/[&<>"]/g, fn);
}
Iman
quelle
0

Ich werde XMLSerializerden Stapel hinzufügen . Es liefert das schnellste Ergebnis ohne Objekt-Caching (weder auf dem Serializer noch auf dem Textknoten).

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

Der zusätzliche Bonus besteht darin, dass Attribute unterstützt werden, die anders als Textknoten serialisiert sind:

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

Sie können sehen, was tatsächlich ersetzt wird, indem Sie die Spezifikation sowohl für Textknoten als auch für Attributwerte überprüfen . Die vollständige Dokumentation enthält mehr Knotentypen, das Konzept ist jedoch dasselbe.

Die Leistung ist am schnellsten, wenn sie nicht zwischengespeichert wird. Wenn Sie das Caching zulassen, ist das Aufrufen innerHTMLeines HTMLElement mit einem untergeordneten Textknoten am schnellsten. Regex wäre am langsamsten (wie aus anderen Kommentaren hervorgeht). Natürlich könnte XMLSerializer in anderen Browsern schneller sein, aber in meinen (eingeschränkten) Tests ist a innerHTMLam schnellsten.


Schnellste einzelne Zeile:

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

Am schnellsten mit Caching:

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

ShortFuse
quelle
-3

Ein bisschen spät zur Show, aber was ist falsch daran, encodeURIComponent () und decodeURIComponent () zu verwenden ?

suncat100
quelle
1
Diese tun etwas völlig Unabhängiges
Callum
1
Vielleicht der größte Missbrauch des Wortes "vollständig", den ich je gehört habe. In Bezug auf die Hauptthemenfrage kann sie beispielsweise verwendet werden, um eine HTML-Zeichenfolge (offensichtlich aus einem bestimmten Speichergrund) unabhängig von HTML-Tags zu dekodieren und sie dann bei Bedarf problemlos wieder in HTML zu codieren.
Suncat100