Metode tercepat untuk melepaskan tag HTML sebagai entitas HTML?

99

Saya menulis ekstensi Chrome yang melibatkan melakukan banyak pekerjaan berikut: membersihkan string yang mungkin berisi tag HTML, dengan mengonversi <, >dan &menjadi &lt;, &gt;dan &amp;, masing-masing.

(Dengan kata lain, sama seperti PHP htmlspecialchars(str, ENT_NOQUOTES)- saya rasa tidak ada kebutuhan nyata untuk mengonversi karakter kutip ganda.)

Ini adalah fungsi tercepat yang saya temukan sejauh ini:

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

Tapi masih ada kelambatan besar ketika saya harus menjalankan beberapa ribu string sekaligus.

Adakah yang bisa memperbaiki ini? Ini sebagian besar untuk string antara 10 dan 150 karakter, jika itu membuat perbedaan.

(Satu gagasan yang saya miliki adalah untuk tidak repot-repot menyandikan tanda lebih besar dari - apakah ada bahaya nyata dengan itu?)

callum
sumber
2
Mengapa? Dalam kebanyakan kasus yang Anda ingin lakukan ini, Anda ingin memasukkan data ke DOM, dalam hal ini Anda harus lupa tentang melarikan diri dan membuat textNode darinya.
Quentin
1
@ David Dorward: mungkin dia ingin membersihkan data POST, dan server tidak melakukan round-trip data dengan benar.
Lie Ryan
4
@Lie - jika demikian, maka solusinya adalah "Demi Pete, perbaiki server karena Anda memiliki lubang XSS yang besar"
Quentin
2
@ David Dorward: ada kemungkinan dia tidak memiliki kendali atas server. Saya pernah mengalami situasi seperti itu baru-baru ini di mana saya sedang menulis skrip greasemonkey untuk mengatasi beberapa hal yang tidak saya sukai di situs web universitas saya; Saya harus melakukan POST di server yang saya tidak memiliki kontrol dan membersihkan data POST menggunakan javascript (karena data mentah berasal dari kotak teks yang kaya, dan begitu juga dengan banyak tag html yang tidak melakukan perjalanan pulang pergi di server) . Admin web mengabaikan permintaan saya agar mereka memperbaiki situs web, jadi saya tidak punya pilihan lain.
Lie Ryan
1
Saya memiliki kasus penggunaan di mana saya perlu menampilkan pesan kesalahan dalam div. Pesan kesalahan dapat berisi HTML dan baris baru. Saya ingin keluar dari HTML dan mengganti baris baru dengan <br>. Kemudian letakkan hasilnya ke dalam div untuk ditampilkan.
mozey

Jawaban:

84

Anda dapat mencoba meneruskan fungsi callback untuk melakukan penggantian:

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

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

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

Berikut ini adalah uji kinerja: http://jsperf.com/encode-html-entities untuk dibandingkan dengan memanggil replacefungsi berulang kali, dan menggunakan metode DOM yang diusulkan oleh Dmitrij.

Jalanmu sepertinya lebih cepat ...

Mengapa Anda membutuhkannya?

Martijn
sumber
2
Tidak perlu melarikan diri >.
6
Sebenarnya jika Anda meletakkan nilai yang lolos dalam atribut elemen html, Anda harus keluar dari simbol>. Jika tidak, itu akan merusak tag untuk elemen html tersebut.
Zlatin Zlatev
1
Dalam teks normal, karakter yang lolos jarang. Lebih baik menelepon ganti hanya jika diperlukan, jika Anda peduli dengan kecepatan maksimal:if (/[<>&"]/.test(str) { ... }
Vitaly
3
@callum: Tidak. Saya tidak tertarik menghitung kasus di mana menurut saya "ada yang salah" (paling tidak karena kasus yang tidak terduga / terlupakanlah yang akan menyakiti Anda, dan saat Anda tidak mengharapkannya). Saya tertarik pada pengkodean dengan standar (jadi kasus yang tidak terduga / terlupakan tidak dapat merugikan Anda secara definisi ). Saya tidak bisa menekankan betapa pentingnya hal ini. >adalah karakter khusus dalam HTML, jadi hindarilah. Sederhana seperti itu. :)
Balapan Ringan di Orbit
4
@LightnessRacesinOrbit Ini relevan karena pertanyaannya adalah apa metode tercepat yang mungkin. Jika mungkin melewatkan >penggantian, itu akan membuatnya lebih cepat.
callum
104

Inilah salah satu cara Anda dapat melakukan ini:

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

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

Berikut demo nya.

Desainer web
sumber
Mendesain ulang demo. Berikut versi layar penuh: jsfiddle.net/Daniel_Hug/qPUEX/show/light
Web_Designer
13
Tidak yakin bagaimana / apa / mengapa - tapi ini jenius.
rob_james
4
Sepertinya ini memanfaatkan kode elemen TextArea yang ada untuk meng-escape teks literal. Sangat bagus, saya pikir trik kecil ini akan menemukan rumah lain.
Ajax
3
@jazkat Saya tidak menggunakan fungsi itu. Variabel escape yang saya gunakan, saya tentukan sendiri dalam contoh.
Web_Designer
2
tetapi apakah ini kehilangan ruang putih dll.
Andrew
31

Metode Martijn sebagai fungsi prototipe:

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
sumber
12
Tambahkan Stringseperti ini, ini harus escapeHtml karena ini bukan pelolosan untuk String pada umumnya. Itu String.escapeHtmlbenar, tetapi String.escapemenimbulkan pertanyaan, "melarikan diri untuk apa?"
Lawrence Dol
3
Ya ide yang bagus. Saya telah menjauh dari memperpanjang prototipe hari ini untuk menghindari konflik.
Aram Kocharyan
1
Jika browser Anda memiliki dukungan untuk Symbol, Anda dapat menggunakannya untuk menghindari pencemaran namespace kunci-string. var escape = Simbol baru ("escape"); String.prototype [escape] = function () {...}; "teks" [escape] ();
Ajax
12

Solusi yang lebih cepat / lebih singkat adalah:

escaped = new Option(html).innerHTML

Ini terkait dengan beberapa sisa JavaScript yang aneh di mana elemen Option mempertahankan konstruktor yang melakukan pelolosan semacam ini secara otomatis.

Penghargaan untuk https://github.com/jasonmoo/t.js/blob/master/t.js

Todd
sumber
1
Satu baris rapi tetapi metode paling lambat setelah regex. Selain itu, teks di sini dapat diberi spasi kosong, menurut spesifikasi
ShortFuse
Perhatikan bahwa tautan "metode paling lambat" dari @ ShortFuse membuat sistem saya kehabisan RAM (dengan ~ 6GB gratis) dan firefox tampaknya berhenti mengalokasikan sebelum kehabisan memori jadi alih-alih mematikan proses yang mengganggu, linux akan duduk di sana dan membiarkan Anda melakukannya sebuah hard power off.
Luc
11

Kode sumber AngularJS juga memiliki versi di dalam 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
sumber
1
Wow, ekspresi reguler non-alphanum itu intens. Saya tidak berpikir | dalam ekspresi itu dibutuhkan.
Ajax
9

Skrip 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

baptx
sumber
Saya tidak downvote, tapi semua regex style replace akan gagal encode unicode ... Jadi, siapa pun yang menggunakan bahasa asing akan kecewa. Trik <textarei> yang disebutkan di atas sangat keren dan menangani semuanya dengan cepat dan aman.
Ajax
Regex berfungsi dengan baik untuk saya dengan sejumlah karakter Unicode non-Latin. Saya tidak akan mengharapkan yang lain. Bagaimana menurut Anda ini tidak akan berhasil? Apakah Anda memikirkan codepages single-byte yang membutuhkan entitas HTML? Itulah gunanya fungsi ke-3 dan ke-4, dan secara eksplisit bukan fungsi ke-1 dan ke-2. Saya suka diferensiasinya.
ygoe
@LonelyPixel Saya rasa dia tidak akan melihat komentar Anda jika Anda tidak menyebut dia ("Hanya satu pengguna tambahan yang dapat diberitahukan; pemilik postingan akan selalu diberi tahu")
baptx
Saya sama sekali tidak tahu ada pemberitahuan yang ditargetkan. @Ajax silakan lihat komentar saya di atas.
ygoe
@LonelyPixel Saya melihat sekarang. Untuk beberapa alasan saya tidak berpikir ada penggantian gaya textarea dalam jawaban ini. Saya memang sedang memikirkan titik kode ganda nilai unicode besar, seperti Mandarin. Maksud saya, mungkin saja membuat regex cukup pintar, tetapi saat Anda melihat pintasan yang dapat diambil oleh vendor browser, saya akan merasa cukup yakin bahwa textarea akan jauh lebih cepat (daripada regex yang sepenuhnya kompeten). Apakah seseorang memposting patokan untuk jawaban ini? Aku bersumpah aku pernah melihatnya.
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
sumber
1

Saya tidak sepenuhnya yakin tentang kecepatan, tetapi jika Anda mencari untuk kesederhanaan saya akan menyarankan menggunakan / garis bawah lodash melarikan diri fungsi.

gilmatic.dll
sumber
0

Metode Martijn sebagai fungsi tunggal dengan penanganan tanda " ( menggunakan dalam javascript ):

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

Saya akan menambahkan XMLSerializerke tumpukan. Ini memberikan hasil tercepat tanpa menggunakan cache objek apa pun (bukan pada serializer, atau pada node Teks).

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

Bonus tambahannya adalah mendukung atribut yang diserialkan secara berbeda dari node teks:

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

Anda dapat melihat apa yang sebenarnya diganti dengan memeriksa spesifikasi, baik untuk node teks dan untuk nilai atribut . Dokumentasi lengkap memiliki lebih banyak tipe node, tetapi konsepnya sama.

Untuk kinerja, ini yang tercepat saat tidak di-cache. Jika Anda mengizinkan caching, maka memanggil innerHTMLHTMLElement dengan node Teks anak adalah yang tercepat. Regex akan menjadi yang paling lambat (seperti yang dibuktikan oleh komentar lain). Tentu saja, XMLSerializer bisa lebih cepat di browser lain, tetapi dalam pengujian (terbatas) saya, yang innerHTMLtercepat.


Garis tunggal tercepat:

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

Tercepat dengan 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
sumber
-3

Agak terlambat untuk pertunjukan, tapi apa salahnya menggunakan encodeURIComponent () dan decodeURIComponent () ?

suncat100
sumber
1
Mereka melakukan sesuatu yang sama sekali tidak berhubungan
callum
1
Mungkin penyalahgunaan terbesar dari kata "sepenuhnya" yang pernah saya dengar. Misalnya, dalam kaitannya dengan pertanyaan topik utama, ini dapat digunakan untuk memecahkan kode string html (jelas untuk beberapa alasan penyimpanan), terlepas dari tag html, dan kemudian dengan mudah menyandikannya kembali ke html lagi bila dan jika diperlukan.
suncat100