Dekode & amp; kembali ke & dalam JavaScript

230

Saya memiliki string seperti

var str = 'One & two & three';

diterjemahkan ke dalam HTML oleh server web. Saya perlu mengubah string itu menjadi

'One & two & three'

Saat ini, itulah yang saya lakukan (dengan bantuan jQuery):

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

Namun saya memiliki perasaan gelisah bahwa saya salah melakukannya. saya telah mencoba

unescape("&")

tetapi tampaknya tidak berfungsi, begitu pula komponen decodeURI / decodeURIC.

Apakah ada cara lain yang lebih asli dan elegan untuk melakukannya?

Seni
sumber
Fungsi besar yang termasuk dalam artikel ini tampaknya berfungsi dengan baik: blogs.msdn.com/b/aoakley/archive/2003/11/12/49645.aspx Saya tidak berpikir itu solusi yang paling pintar tetapi berfungsi.
Matias
1
Karena string yang mengandung entitas HTML adalah sesuatu yang berbeda dari string yang dikodekanescape d atau URI , fungsi-fungsi itu tidak akan berfungsi.
Marcel Korpel
1
@Matias mencatat bahwa entitas bernama baru telah ditambahkan ke HTML (mis. Via HTML 5 spec) karena fungsi itu ditulis pada tahun 2003 - misalnya, ia tidak mengenali 𝕫. Ini adalah masalah dengan spec yang berkembang; dengan demikian, Anda harus memilih alat yang benar-benar dipertahankan untuk menyelesaikannya.
Mark Amery
1
@MarkAmery ya, saya sangat setuju! Merupakan pengalaman yang menyenangkan untuk kembali ke pertanyaan ini setelah beberapa tahun, terima kasih!
Matias

Jawaban:

105

Opsi yang lebih modern untuk menafsirkan HTML (teks dan lainnya) dari JavaScript adalah dukungan HTML di DOMParserAPI ( lihat di sini di MDN ). Ini memungkinkan Anda untuk menggunakan parser HTML asli peramban untuk mengonversi string ke dokumen HTML. Ini telah didukung dalam versi baru dari semua browser utama sejak akhir 2014.

Jika kita hanya ingin mendekodekan beberapa konten teks, kita dapat meletakkannya sebagai satu-satunya konten dalam badan dokumen, parsing dokumen, dan mengeluarkan isinya .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);

Kita dapat melihat dalam spesifikasi draf untukDOMParser JavaScript yang tidak diaktifkan untuk dokumen yang diuraikan, sehingga kami dapat melakukan konversi teks ini tanpa masalah keamanan.

The parseFromString(str, type)Metode harus menjalankan langkah-langkah ini, tergantung pada jenis :

  • "text/html"

    Parsing str dengan HTML parser, dan kembalikan yang baru dibuat Document.

    Bendera skrip harus disetel ke "dinonaktifkan".

    CATATAN

    scriptelemen ditandai tidak dapat dieksekusi dan isi noscriptdiuraikan sebagai markup.

Ini di luar cakupan pertanyaan ini, tetapi harap perhatikan bahwa jika Anda mengambil sendiri simpul DOM yang diurai (bukan hanya konten teks mereka) dan memindahkannya ke dokumen DOM yang hidup, ada kemungkinan bahwa skrip mereka akan diaktifkan kembali, dan mungkin ada menjadi masalah keamanan. Saya belum merisetnya, jadi harap berhati-hati.

Jeremy Banks
sumber
5
ada alternatif untuk NodeJs?
coderInrRain
285

Apakah Anda perlu mendekode semua entitas HTML yang disandikan atau hanya &amp;dirinya sendiri?

Jika Anda hanya perlu menangani &amp;maka Anda bisa melakukan ini:

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

Jika Anda perlu mendekode semua entitas HTML maka Anda dapat melakukannya tanpa jQuery:

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

Harap perhatikan komentar Mark di bawah ini yang menyoroti celah keamanan dalam versi yang lebih awal dari jawaban ini dan merekomendasikan penggunaan textareadaripada divuntuk mengurangi potensi kerentanan XSS. Kerentanan ini ada apakah Anda menggunakan jQuery atau JavaScript biasa.

LukeH
sumber
16
Awas! Ini berpotensi tidak aman. Jika encoded='<img src="bla" onerror="alert(1)">'kemudian cuplikan di atas akan menampilkan peringatan. Ini berarti jika teks Anda yang dikodekan berasal dari input pengguna, mendekode dengan potongan ini dapat menghadirkan kerentanan XSS.
Mark Amery
@MarkAmery Saya bukan ahli keamanan, tetapi sepertinya jika Anda langsung mengatur div ke nullsetelah mendapatkan teks, peringatan di img tidak dipecat - jsfiddle.net/Mottie/gaBeb/128
Mottie
4
@Mottie perhatikan browser mana yang cocok untuk Anda, tetapi alert(1)masih menyala untuk saya di Chrome di OS X. Jika Anda ingin varian aman dari hack ini, coba gunakan atextarea .
Mark Amery
+1 untuk pengganti regexp pengganti sederhana hanya untuk satu jenis entitas html. Jangan gunakan ini jika Anda mengharapkan data html diinterpolasi dari, katakanlah, aplikasi termos python ke templat.
OzzyTheGiant
Bagaimana melakukan ini di server Node?
Mohammad Kermani
44

Matthias Bynens memiliki perpustakaan untuk ini: https://github.com/mathiasbynens/he

Contoh:

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

Saya sarankan mendukungnya daripada peretasan yang melibatkan pengaturan konten HTML suatu elemen dan kemudian membaca kembali konten teksnya. Pendekatan semacam itu bisa berhasil, tetapi sangat berbahaya dan memberikan peluang XSS jika digunakan pada input pengguna yang tidak dipercaya.

Jika Anda benar-benar tidak tega memuat di perpustakaan, Anda dapat menggunakan textarearetasan yang dijelaskan dalam jawaban ini untuk pertanyaan hampir duplikat, yang, tidak seperti berbagai pendekatan serupa yang telah disarankan, tidak memiliki celah keamanan yang saya ketahui:

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

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

Tetapi perhatikan masalah keamanan, yang memengaruhi pendekatan serupa dengan yang ini, yang saya cantumkan dalam jawaban terkait! Pendekatan ini adalah peretasan, dan perubahan di masa depan terhadap konten yang diizinkan textarea(atau bug pada browser tertentu) dapat menyebabkan kode yang bergantung padanya tiba-tiba memiliki lubang XSS suatu hari.

Mark Amery
sumber
Perpustakaan Matthias Bynens hebenar-benar hebat! Terima kasih banyak atas rekomendasinya!
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
    };
})();

Ini dari kode sumber ExtJS.

WaiKit Kung
sumber
4
-1; ini gagal menangani sebagian besar entitas bernama. Misalnya, htmlEnDecode.htmlDecode('&euro;')harus kembali '€', tetapi sebaliknya kembali '&euro;'.
Mark Amery
17

element.innerText juga melakukan trik.

avg_joe
sumber
15

Anda dapat menggunakan fungsi Lodash unescape / escape https://lodash.com/docs/4.17.5#unescape

import unescape from 'lodash/unescape';

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

str akan menjadi 'fred, barney, & pebbles'

Saya adalah saya
sumber
1
mungkin lebih baik untuk melakukan "impor _unescape dari 'lodash / unescape';" sehingga tidak bertentangan dengan fungsi javascript usang dengan nama yang sama: unescape
Rick Penabella
14

Jika Anda mencarinya, seperti saya - sementara itu ada metode JQuery yang bagus dan aman.

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

Anda bisa f.ex. ketikkan ini di konsol Anda:

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

Jadi $ .parseHTML (x) mengembalikan sebuah array, dan jika Anda memiliki markup HTML dalam teks Anda, array.length akan lebih besar dari 1.

cslotty
sumber
Bekerja dengan sempurna untuk saya, ini persis apa yang saya cari, terima kasih.
Jonathan Nielsen
1
Jika xmemiliki nilai <script>alert('hello');</script>di atas akan crash. Di jQuery saat ini, ia tidak akan benar-benar mencoba menjalankan skrip, tetapi [0]akan menghasilkan undefinedsehingga panggilan ke textContentakan gagal dan skrip Anda akan berhenti di situ. $('<div />').html(x).text();terlihat lebih aman - via gist.github.com/jmblog/3222899
Andrew Hodgkinson
@AndrewHodgkinson ya, tapi pertanyaannya adalah "Decode & amp; kembali ke & dalam JavaScript" - jadi Anda akan menguji konten x pertama atau pastikan Anda hanya menggunakannya dalam kasus yang benar.
cslotty
Saya tidak benar-benar melihat bagaimana hal itu terjadi. Kode di atas berfungsi dalam semua kasus. Dan seberapa tepatnya Anda "memastikan" nilai x yang perlu diperbaiki? Dan bagaimana jika contoh skrip di atas mengingatkan '& amp;' sehingga benar-benar perlu koreksi? Kami tidak tahu dari mana asal OP, jadi masukan jahat harus dipertimbangkan.
Andrew Hodgkinson
@AndrewHodgkinson Saya suka pertimbangan Anda, tapi itu bukan pertanyaan di sini. Jangan ragu untuk menjawab pertanyaan itu. Saya kira Anda dapat menghapus tag skrip, f.ex.
cslotty
8

jQuery akan menyandikan dan mendekode untuk Anda. Namun, Anda perlu menggunakan tag textarea, bukan 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
sumber
2
-1 karena ada lubang keamanan (mengejutkan) di sini untuk versi jQuery lama, beberapa di antaranya mungkin masih memiliki basis pengguna yang signifikan - versi tersebut akan mendeteksi dan secara eksplisit mengevaluasi skrip dalam HTML yang diteruskan .html(). Jadi bahkan menggunakan a textareatidak cukup untuk memastikan keamanan di sini; Saya sarankan tidak menggunakan jQuery untuk tugas ini dan menulis kode yang setara dengan API DOM biasa . (Ya, perilaku lama oleh jQuery itu gila dan mengerikan.)
Mark Amery
Terima kasih telah menunjukkannya. Namun, pertanyaannya tidak termasuk persyaratan untuk memeriksa injeksi skrip. Pertanyaan khusus menanyakan tentang html yang diberikan oleh server web. Konten html yang disimpan ke server web mungkin harus divalidasi untuk injeksi skrip sebelum disimpan.
Jason Williams
4

Pertama buat suatu <span id="decodeIt" style="display:none;"></span>tempat di tubuh

Selanjutnya, tetapkan string yang akan diterjemahkan sebagai innerHTML untuk ini:

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

Akhirnya,

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

Ini adalah kode keseluruhan:

var stringtodecode="<B>Hello</B> world<br>";
document.getElementById("decodeIt").innerHTML=stringtodecode;
stringtodecode=document.getElementById("decodeIt").innerText
Infoglaze.com
sumber
1
-1; ini sangat tidak aman untuk digunakan pada input yang tidak dipercaya. Misalnya, pertimbangkan apa yang terjadi jika stringtodecodemengandung sesuatu seperti <script>alert(1)</script>.
Mark Amery
2

solusi javascript yang menangkap yang umum:

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

ini kebalikan dari https://stackoverflow.com/a/4835406/2738039

Peter Brandt
sumber
Jika Anda menggunakan yang map[c] || ''tidak dikenal tidak akan ditampilkan sebagaiundefined
Eldelshell
Cakupan yang sangat terbatas; -1.
Mark Amery
2
+1, lebih banyakunescapeHtml(str){ var map = {amp: '&', lt: '<', le: '≤', gt: '>', ge: '≥', quot: '"', '#039': "'"} return str.replace(/&([^;]+);/g, (m, c) => map[c]|| '') }
Trần Quốc Hoài baru 2015
Cakupan manual. Tidak direkomendasikan.
Sergio A.
2

Untuk cowok satu baris:

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

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

Pertanyaannya tidak menentukan asal dari xtetapi masuk akal untuk mempertahankan, jika kita bisa, terhadap masukan berbahaya (atau hanya tidak terduga, dari aplikasi kita sendiri). Misalnya, anggaplah xmemiliki nilai &amp; <script>alert('hello');</script>. Cara aman dan sederhana untuk menangani ini di jQuery adalah:

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

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

Ditemukan melalui https://gist.github.com/jmblog/3222899 . Saya tidak bisa melihat banyak alasan untuk menghindari menggunakan solusi ini mengingat setidaknya itu pendek, jika tidak lebih pendek dari beberapa alternatif dan memberikan pertahanan terhadap XSS.

(Saya awalnya memposting ini sebagai komentar, tetapi saya menambahkannya sebagai jawaban karena komentar berikutnya di utas yang sama meminta saya melakukannya).

Andrew Hodgkinson
sumber
1

Saya mencoba segalanya untuk menghapus & dari array JSON. Tidak ada contoh di atas, tetapi https://stackoverflow.com/users/2030321/chris memberikan solusi hebat yang membuat saya memperbaiki masalah saya.

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

Saya tidak menggunakan, karena saya tidak mengerti bagaimana cara memasukkannya ke jendela modal yang menarik data JSON ke dalam array, tapi saya coba ini berdasarkan contoh, dan itu berhasil:

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

Saya suka karena sederhana, dan berfungsi, tetapi tidak yakin mengapa itu tidak banyak digunakan. Dicari hai & rendah untuk menemukan solusi sederhana. Saya terus mencari pemahaman tentang sintaks, dan jika ada risiko menggunakan ini. Belum menemukan apa pun.

Digexart
sumber
Proposal pertama Anda hanya sedikit rumit, tetapi bekerja dengan baik tanpa banyak usaha. Yang kedua, di sisi lain, hanya menggunakan kekuatan kasar untuk memecahkan kode karakter; ini berarti perlu banyak upaya dan waktu untuk menyelesaikan fungsi decoding penuh. Itu sebabnya tidak ada yang menggunakan cara itu untuk menyelesaikan masalah OP.
Sergio A.