Il metodo più veloce per sfuggire ai tag HTML come entità HTML?


98

Sto scrivendo un'estensione Chrome che coinvolge facendo un sacco di lavoro seguente: sanificazione stringhe che potrebbero contenere tag HTML, convertendo <, >e &per &lt;, &gt;e &amp;, rispettivamente.

(In altre parole, lo stesso di PHP htmlspecialchars(str, ENT_NOQUOTES)- non penso che ci sia alcuna reale necessità di convertire i caratteri di virgolette doppie.)

Questa è la funzione più veloce che ho trovato finora:

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

Ma c'è ancora un grande ritardo quando devo eseguire alcune migliaia di stringhe in una volta sola.

Qualcuno può migliorare su questo? È principalmente per stringhe tra 10 e 150 caratteri, se questo fa la differenza.

(Un'idea che avevo era di non preoccuparmi di codificare il segno di maggiore di - ci sarebbe stato un vero pericolo con quello?)


2
Perché? Nella maggior parte dei casi che vuoi fare, vuoi inserire i dati nel DOM, nel qual caso dovresti dimenticarti di sfuggirli e creare un textNode da esso.
Quentin

1
@ David Dorward: forse voleva disinfettare i dati POST e il server non esegue correttamente il round trip dei dati.
Lie Ryan

4
@Lie - in tal caso, la soluzione è "Per l'amor di Pete, aggiusta il server perché hai un grosso buco XSS"
Quentin

2
@David Dorward: è possibile che non abbia il controllo sul server. Recentemente mi sono trovato in una situazione del genere in cui stavo scrivendo uno script greasemonkey per aggirare un paio di cose che non mi piacciono nel sito web della mia università; Ho dovuto fare un POST su un server su cui non ho il controllo e disinfettare i dati POST usando javascript (poiché i dati grezzi provengono da una ricca casella di testo, e quindi ha un sacco di tag html che non fanno round trip sul server) . L'amministratore web stava ignorando la mia richiesta di riparare il sito Web, quindi non avevo altra scelta.
Lie Ryan

1
Ho un caso d'uso in cui devo visualizzare un messaggio di errore in un div. Il messaggio di errore può contenere HTML e nuove righe. Voglio sfuggire all'HTML e sostituire le nuove righe con <br>. Quindi inserisci il risultato in un div per la visualizzazione.
mozey

Risposte:


83

Potresti provare a passare una funzione di callback per eseguire la sostituzione:

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

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

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

Ecco un test delle prestazioni: http://jsperf.com/encode-html-entities da confrontare con la chiamata replaceripetuta della funzione e l'utilizzo del metodo DOM proposto da Dmitrij.

La tua strada sembra essere più veloce ...

Perché ne hai bisogno, però?


2
Non c'è bisogno di scappare >.

6
In realtà, se metti il ​​valore di escape nell'attributo di un elemento html, devi uscire dal simbolo>. Altrimenti interromperebbe il tag per quell'elemento html.
Zlatin Zlatev

1
Nel testo normale i caratteri con escape sono rari. È meglio chiamare sostituire solo quando necessario, se ti interessa la velocità massima:if (/[<>&"]/.test(str) { ... }
Vitaly

3
@callum: No. Non mi interessa enumerare i casi in cui penso che "qualcosa potrebbe andare storto" (anche perché sono i casi inattesi / dimenticati che ti feriranno, e quando meno te lo aspetti). Sono interessato alla codifica secondo gli standard (quindi i casi inattesi / dimenticati non possono ferirti per definizione ). Non posso sottolineare quanto sia importante questo. >è un carattere speciale in HTML, quindi esegui l'escape. Semplice come quella. :)
Gare di leggerezza in orbita

4
@LightnessRacesinOrbit È rilevante perché la domanda è qual è il metodo più veloce possibile. Se è possibile saltare la >sostituzione, ciò lo renderebbe più veloce.
callum

104

Ecco un modo per farlo:

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

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

Ecco una demo.


Ridisegnata la demo. Ecco una versione a schermo intero: jsfiddle.net/Daniel_Hug/qPUEX/show/light
Web_Designer

13
Non sono sicuro di come / cosa / perché - ma questo è geniale.
rob_james

4
Sembra che stia sfruttando il codice esistente dell'elemento TextArea per l'escape del testo letterale. Molto carino, penso che questo piccolo trucco troverà un'altra casa.
Ajax

3
@jazkat Non sto usando quella funzione. La variabile di escape che uso, mi definisco nell'esempio.
Web_Designer

2
ma questo perde spazi bianchi ecc.
Andrew

31

Il metodo di Martijn come funzione prototipo:

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;"

12
Aggiungi a Stringquesto dovrebbe essere escapeHtml poiché non è un escape per una stringa in generale. Questo è String.escapeHtmlcorretto, ma String.escapesolleva la domanda "fuga per cosa?"
Lawrence Dol

3
Sì, buona idea. In questi giorni mi sono allontanato dall'estensione del prototipo per evitare conflitti.
Aram Kocharyan

1
Se il tuo browser ha il supporto per Symbol, puoi usarlo invece per evitare di inquinare lo spazio dei nomi della chiave di stringa. var escape = new Symbol ("escape"); String.prototype [escape] = function () {...}; "testo" [escape] ();
Ajax

12

Una soluzione ancora più rapida / breve è:

escaped = new Option(html).innerHTML

Ciò è correlato ad alcune strane vestigia di JavaScript per cui l'elemento Option mantiene un costruttore che esegue questo tipo di escape automaticamente.

Ringraziamo https://github.com/jasonmoo/t.js/blob/master/t.js


1
Neat one-liner ma il metodo più lento dopo regex. Inoltre, il testo qui può avere spazi
vuoti rimossi

Nota che il collegamento "metodo più lento" di @ ShortFuse fa esaurire la RAM del mio sistema (con ~ 6 GB liberi) e firefox sembra smettere di allocare appena prima che la memoria sia esaurita quindi invece di uccidere il processo incriminato, linux si siederà lì e te lo lascerà fare un hard power off.
Luc

11

Il codice sorgente di AngularJS ha anche una versione all'interno di 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;');
}

1
Wow, quella regex non alfa è intensa. Non credo che il | nell'espressione è necessario però.
Ajax


9

Script all-in-one:

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


Non ho downvote, ma tutte le sostituzioni in stile regex non codificheranno l'unicode ... Quindi, chiunque utilizzi una lingua straniera rimarrà deluso. Il trucco <textarea> menzionato sopra è davvero interessante e gestisce tutto in modo rapido e sicuro.
Ajax

La regex funziona bene per me con un numero di caratteri Unicode non latini. Non mi aspetto nient'altro. Come pensi che non funzionerebbe? Stai pensando a tabelle codici a byte singolo che richiedono entità HTML? Ecco a cosa servono la terza e la quarta funzione, e non esplicitamente la prima e la seconda. Mi piace la differenziazione.
ygoe

@LonelyPixel Non credo che vedrà il tuo commento se non lo menzioni ("Solo un utente aggiuntivo può essere avvisato; il proprietario del post verrà sempre informato")
baptx

Non sapevo affatto che esistessero notifiche mirate. @ Ajax si prega di vedere il mio commento sopra.
ygoe

@LonelyPixel che vedo ora. Per qualche motivo non pensavo ci fosse una sostituzione in stile textarea in questa risposta. Stavo, infatti, pensando a valori Unicode doppi a punti di codice, come il mandarino. Voglio dire, sarebbe possibile creare un'espressione regolare abbastanza intelligente, ma quando guardi le scorciatoie che i fornitori di browser possono prendere, mi sentirei abbastanza bene a scommettere che textarea sarà molto più veloce (di una regex completamente competente). Qualcuno ha pubblicato un benchmark su questa risposta? Ho giurato di averne visto uno.
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>


1

Non sono del tutto sicuro della velocità, ma se stai cercando semplicità ti suggerirei di usare la funzione di escape lodash / underscore .


0

Il metodo di Martijn come singola funzione con manipolazione " mark ( utilizzando in javascript ):

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

0

Aggiungerò XMLSerializeralla pila. Fornisce il risultato più veloce senza utilizzare alcun oggetto di memorizzazione nella cache (non sul serializzatore, né sul nodo Testo).

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

Il vantaggio aggiuntivo è che supporta attributi serializzati in modo diverso rispetto ai nodi di testo:

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

Puoi vedere cosa sta effettivamente sostituendo controllando le specifiche, sia per i nodi di testo che per i valori degli attributi . La documentazione completa ha più tipi di nodo, ma il concetto è lo stesso.

Per quanto riguarda le prestazioni, è il più veloce quando non è memorizzato nella cache. Quando si consente la memorizzazione nella cache, la chiamata innerHTMLa un HTMLElement con un nodo di testo figlio è più veloce. Regex sarebbe il più lento (come dimostrato da altri commenti). Ovviamente, XMLSerializer potrebbe essere più veloce su altri browser, ma nei miei (limitati) test, a innerHTMLè il più veloce.


Riga singola più veloce:

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

Più veloce con la memorizzazione nella 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


-3

Un po 'tardi per lo spettacolo, ma cosa c'è di sbagliato nell'usare encodeURIComponent () e decodeURIComponent () ?


1
Quelli fanno qualcosa di completamente estraneo
callum

1
Forse il più grande abuso della parola "completamente" che abbia mai sentito. Ad esempio, in relazione alla domanda dell'argomento principale, potrebbe essere utilizzato per decodificare una stringa html (ovviamente per qualche motivo di archiviazione), indipendentemente dai tag html, e quindi codificarla facilmente di nuovo in html quando e se necessario.
suncat100
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.