Unescape HTML encje w Javascript?

177

Mam kod JavaScript, który komunikuje się z zapleczem XML-RPC. XML-RPC zwraca ciągi w postaci:

<img src='myimage.jpg'>

Jednak kiedy używam JavaScript do wstawiania ciągów do HTML, renderują się one dosłownie. Nie widzę obrazu, dosłownie widzę ciąg:

<img src='myimage.jpg'>

Domyślam się, że kod HTML jest usuwany przez kanał XML-RPC.

Jak mogę usunąć ciąg znaków w Javascript? Wypróbowałem techniki na tej stronie, bezskutecznie: http://paulschreiber.com/blog/2008/09/20/javascript-how-to-unescape-html-entities/

Jakie są inne sposoby zdiagnozowania problemu?

Józefa Turiana
źródło

Odpowiedzi:

177

EDYCJA: Powinieneś używać DOMParser API, jak sugeruje Wladimir. Edytowałem moją poprzednią odpowiedź, ponieważ opublikowana funkcja wprowadziła lukę w zabezpieczeniach.

Poniższy fragment to kod starej odpowiedzi z niewielką modyfikacją: użycie a textareazamiast a divzmniejsza podatność na XSS, ale nadal jest problematyczne w IE9 i Firefox.

function htmlDecode(input){
  var e = document.createElement('textarea');
  e.innerHTML = input;
  // handle case of empty input
  return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}

htmlDecode("&lt;img src='myimage.jpg'&gt;"); 
// returns "<img src='myimage.jpg'>"

Zasadniczo tworzę programowo element DOM, przypisuję zakodowany kod HTML do jego innerHTML i pobieram wartość nodeValue z węzła tekstowego utworzonego przy wstawieniu innerHTML. Ponieważ po prostu tworzy element, ale nigdy go nie dodaje, żaden kod HTML witryny nie jest modyfikowany.

Będzie działać na różnych przeglądarkach (w tym starszych przeglądarkach) i akceptować wszystkie jednostki znaków HTML .

EDYCJA: Stara wersja tego kodu nie działała w IE z pustymi danymi wejściowymi, jak pokazano tutaj na jsFiddle (widok w IE). Powyższa wersja działa ze wszystkimi wejściami.

AKTUALIZACJA: wygląda na to, że nie działa to z dużym ciągiem znaków, a także wprowadza lukę w zabezpieczeniach , patrz komentarze.

CMS
źródło
Rozumiem, zmieniłeś się na „, więc pozwól mi usunąć mój komentarz z powrotem, dzięki, działa świetnie, +1
TY
1
@ S.Mark: &apos;nie należy do HTML 4 Entities, dlatego! w3.org/TR/html4/sgml/entities.html fishbowl.pastiche.org/2003/07/01/the_curse_of_apos
CMS
2
Zobacz także uwagę @ kender na temat słabego bezpieczeństwa tego podejścia.
Joseph Turian
2
Zobacz moją notatkę dla @kendera o kiepskich testach, które zrobił;)
Roatin Marth,
24
Ta funkcja jest zagrożeniem dla bezpieczeństwa, kod JavaScript będzie działał nawet pomimo tego, że element nie zostanie dodany do DOM. Jest to więc coś do użycia tylko wtedy, gdy ciąg wejściowy jest zaufany. Dodałem własną odpowiedź wyjaśniającą problem i zapewniającą bezpieczne rozwiązanie. Efektem ubocznym jest to, że wynik nie jest ucinany, jeśli istnieje wiele węzłów tekstowych.
Wladimir Palant
375

Większość udzielonych tutaj odpowiedzi ma ogromną wadę: jeśli ciąg, który próbujesz przekonwertować, nie jest zaufany, otrzymasz lukę w zabezpieczeniach Cross-Site Scripting (XSS) . W przypadku funkcji w zaakceptowanej odpowiedzi rozważ następujące kwestie:

htmlDecode("<img src='dummy' onerror='alert(/xss/)'>");

Łańcuch zawiera tutaj znacznik HTML bez zmiany znaczenia, więc zamiast dekodowania czegokolwiek htmlDecodefunkcja w rzeczywistości uruchomi kod JavaScript określony w ciągu.

Można tego uniknąć, używając DOMParser, który jest obsługiwany we wszystkich nowoczesnych przeglądarkach :

function htmlDecode(input) {
  var doc = new DOMParser().parseFromString(input, "text/html");
  return doc.documentElement.textContent;
}

console.log(  htmlDecode("&lt;img src='myimage.jpg'&gt;")  )    
// "<img src='myimage.jpg'>"

console.log(  htmlDecode("<img src='dummy' onerror='alert(/xss/)'>")  )  
// ""

Ta funkcja gwarantuje, że jako efekt uboczny nie uruchomi się żadnego kodu JavaScript. Wszelkie tagi HTML zostaną zignorowane, zwrócona zostanie tylko treść tekstowa.

Uwaga dotycząca zgodności : parsowanie HTML z DOMParserwymaga co najmniej Chrome 30, Firefox 12, Opera 17, Internet Explorer 10, Safari 7.1 lub Microsoft Edge. Tak więc wszystkie przeglądarki bez wsparcia są daleko poza ich EOL, a od 2017 roku jedynymi, które wciąż można zobaczyć na wolności, są starsze wersje Internet Explorera i Safari (zwykle nie są one wystarczająco liczne, aby przeszkadzać).

Wladimir Palant
źródło
19
Myślę, że ta odpowiedź jest najlepsza, ponieważ wspomina o luce w zabezpieczeniach XSS.
Константин Ван
2
Zauważ, że (zgodnie z twoją informacją) DOMParsernie obsługiwał "text/html"przeglądarki Firefox przed wersjąDOMParser.prototype.parseFromString() 12.0 i nadal istnieją najnowsze wersje przeglądarek, które nawet nie obsługują . Zgodnie z twoją referencją, DOMParserjest to wciąż technologia eksperymentalna, a stand-iny wykorzystują innerHTMLwłaściwość, która, jak również wskazałeś w odpowiedzi na moje podejście , ma tę lukę w zabezpieczeniach XSS (która powinna zostać naprawiona przez dostawców przeglądarek).
PointedEars
4
@PointedEars: Kogo obchodzi Firefox 12 w 2016 roku? Problematyczne są Internet Explorer do 9.0 i Safari do 7.0. Jeśli ktoś może sobie pozwolić na nie wspieranie ich (miejmy nadzieję, że wkrótce wszyscy), DOMParser jest najlepszym wyborem. Jeśli nie - tak, opcją byłoby tylko przetwarzanie podmiotów.
Wladimir Palant
4
@PointedEars: <script>tagi, które nie są wykonywane, nie są mechanizmem bezpieczeństwa, ta reguła po prostu pozwala uniknąć skomplikowanych problemów z synchronizacją, jeśli ustawienie innerHTMLmogłoby spowodować uruchomienie synchronicznych skryptów jako efekt uboczny. Oczyszczanie kodu HTML jest trudną sprawą i innerHTMLnawet nie próbuje - już dlatego, że strona internetowa może faktycznie zamierzać ustawić wbudowane programy obsługi zdarzeń. To po prostu nie jest mechanizm przeznaczony do niebezpiecznych danych, kropka.
Wladimir Palant
1
@ ИльяЗеленько: Czy planujesz używać tego kodu w ścisłej pętli lub dlaczego wydajność ma znaczenie? Twoja odpowiedź jest ponownie podatna na XSS, czy naprawdę było warto?
Wladimir Palant
37

Jeśli używasz jQuery:

function htmlDecode(value){ 
  return $('<div/>').html(value).text(); 
}

W przeciwnym razie użyj obiektu enkodera Strictly Software , który ma doskonałą htmlDecode()funkcję.

Chris Fulstow
źródło
59
Nie (powtarzaj NIE) używaj tego do treści generowanych przez użytkowników innych niż treści generowane przez tego użytkownika. Jeśli wartość zawiera znacznik <script>, zawartość skryptu zostanie wykonana!
Malvolio
Nie mogę znaleźć na to licencji w żadnym miejscu na stronie. Czy wiesz, jaka jest licencja?
TRiG
W nagłówku źródła jest licencja, to jest GPL.
Chris Fulstow,
6
TAK, ta funkcja otwiera drogę dla XSS: wypróbuj htmlDecode ("<script> alert (12) </script> 123 & gt;")
Dinis Cruz
jakie jest znaczenie $ ('<div />') ?
Echo Yang
13

Sztuczka polega na wykorzystaniu mocy przeglądarki do dekodowania specjalnych znaków HTML, ale nie pozwala przeglądarce na wykonanie wyników tak, jakby to był rzeczywisty html ... Ta funkcja używa wyrażenia regularnego do identyfikacji i zamiany zakodowanych znaków HTML, jeden znak na czas.

function unescapeHtml(html) {
    var el = document.createElement('div');
    return html.replace(/\&[#0-9a-z]+;/gi, function (enc) {
        el.innerHTML = enc;
        return el.innerText
    });
}
Ben White
źródło
Wyrażenie regularne można dopasować nieco ściślej, /\&#?[0-9a-z]+;/giponieważ # powinno pojawiać się tylko jako drugi znak, jeśli w ogóle.
TheAtomicOption
To najlepsza odpowiedź. Pozwala uniknąć podatności na XSS i nie usuwa znaczników HTML.
Emmanuel
6

Odpowiedź CMS działa dobrze, chyba że kod HTML, który chcesz usunąć, jest bardzo długi, dłuższy niż 65536 znaków. Ponieważ wtedy w Chrome wewnętrzny kod HTML zostaje podzielony na wiele węzłów podrzędnych, z których każdy ma maksymalnie 65536 długości i trzeba je połączyć. Ta funkcja działa również dla bardzo długich ciągów:

function unencodeHtmlContent(escapedHtml) {
  var elem = document.createElement('div');
  elem.innerHTML = escapedHtml;
  var result = '';
  // Chrome splits innerHTML into many child nodes, each one at most 65536.
  // Whereas FF creates just one single huge child node.
  for (var i = 0; i < elem.childNodes.length; ++i) {
    result = result + elem.childNodes[i].nodeValue;
  }
  return result;
}

Zobacz tę odpowiedź na temat innerHTMLmaksymalnej długości, aby uzyskać więcej informacji: https://stackoverflow.com/a/27545633/694469

KajMagnus
źródło
3

Nie jest to bezpośrednia odpowiedź na twoje pytanie, ale czy nie byłoby lepiej, gdyby RPC zwrócił jakąś strukturę (czy to XML, JSON czy cokolwiek innego) z tymi danymi obrazu (adresy URL w twoim przykładzie) wewnątrz tej struktury?

Następnie możesz po prostu przeanalizować go w swoim javascript i zbudować za <img>pomocą samego javascript.

Struktura otrzymana od RPC może wyglądać następująco:

{"img" : ["myimage.jpg", "myimage2.jpg"]}

Myślę, że tak jest lepiej, ponieważ wstrzyknięcie kodu pochodzącego z zewnętrznego źródła na twoją stronę nie wygląda na zbyt bezpieczne. Wyobrażanie sobie kogoś, kto porywa twój skrypt XML-RPC i umieszcza w nim coś, czego nie chcesz (nawet javascript ...)

kender
źródło
Czy powyższe podejście @CMS ma tę lukę w zabezpieczeniach?
Joseph Turian
Właśnie sprawdziłem następujący argument przekazany do funkcji htmlDecode: htmlDecode ("& lt; img src = 'myimage.jpg' & gt; & lt; script & gt; document.write ('xxxxx'); & lt; / script & gt;") i tworzy element <script> </script>, który może być zły, imho. I nadal uważam, że zwrócenie struktury zamiast wstawianego tekstu jest lepsze, na przykład można ładnie obsługiwać błędy.
kender
1
Po prostu próbowałem htmlDecode("&lt;img src='myimage.jpg'&gt;&lt;script&gt;alert('xxxxx');&lt;/script&gt;")i nic się nie stało. Otrzymałem z powrotem zdekodowany ciąg html zgodnie z oczekiwaniami.
Roatin Marth
2

Odpowiedź Chrisa jest ładna i elegancka, ale zawodzi, jeśli wartość jest nieokreślona . Wystarczy prosta poprawa, aby było solidne:

function htmlDecode(value) {
   return (typeof value === 'undefined') ? '' : $('<div/>').html(value).text();
}
nerijus
źródło
Jeśli się poprawi, zrób:return (typeof value !== 'string') ? '' : $('<div/>').html(value).text();
SynCap
2

Nie ma za co ... po prostu posłaniec ... pełne uznanie trafia do ourcodeworld.com, link poniżej.

window.htmlentities = {
        /**
         * Converts a string to its html characters completely.
         *
         * @param {String} str String with unescaped HTML characters
         **/
        encode : function(str) {
            var buf = [];

            for (var i=str.length-1;i>=0;i--) {
                buf.unshift(['&#', str[i].charCodeAt(), ';'].join(''));
            }

            return buf.join('');
        },
        /**
         * Converts an html characterSet into its original character.
         *
         * @param {String} str htmlSet entities
         **/
        decode : function(str) {
            return str.replace(/&#(\d+);/g, function(match, dec) {
                return String.fromCharCode(dec);
            });
        }
    };

Pełny kredyt: https://ourcodeworld.com/articles/read/188/encode-and-decode-html-entities-using-pure-javascript

dekoder7283
źródło
2

To najbardziej kompleksowe rozwiązanie, jakie do tej pory wypróbowałem:

const STANDARD_HTML_ENTITIES = {
    nbsp: String.fromCharCode(160),
    amp: "&",
    quot: '"',
    lt: "<",
    gt: ">"
};

const replaceHtmlEntities = plainTextString => {
    return plainTextString
        .replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec))
        .replace(
            /&(nbsp|amp|quot|lt|gt);/g,
            (a, b) => STANDARD_HTML_ENTITIES[b]
        );
};
Daniel
źródło
„Najbardziej wszechstronny”? Czy próbowałeś porównać go z faktycznie wszechstronnym zestawem testów ?
Dan Dascalescu
1

Byłem na tyle szalony, aby przejść przez tę funkcję, która powinna być ładna, jeśli nie całkowicie, wyczerpująca:

function removeEncoding(string) {
    return string.replace(/&Agrave;/g, "À").replace(/&Aacute;/g, "Á").replace(/&Acirc;/g, "Â").replace(/&Atilde;/g, "Ã").replace(/&Auml;/g, "Ä").replace(/&Aring;/g, "Å").replace(/&agrave;/g, "à").replace(/&acirc;/g, "â").replace(/&atilde;/g, "ã").replace(/&auml;/g, "ä").replace(/&aring;/g, "å").replace(/&AElig;/g, "Æ").replace(/&aelig;/g, "æ").replace(/&szlig;/g, "ß").replace(/&Ccedil;/g, "Ç").replace(/&ccedil;/g, "ç").replace(/&Egrave;/g, "È").replace(/&Eacute;/g, "É").replace(/&Ecirc;/g, "Ê").replace(/&Euml;/g, "Ë").replace(/&egrave;/g, "è").replace(/&eacute;/g, "é").replace(/&ecirc;/g, "ê").replace(/&euml;/g, "ë").replace(/&#131;/g, "ƒ").replace(/&Igrave;/g, "Ì").replace(/&Iacute;/g, "Í").replace(/&Icirc;/g, "Î").replace(/&Iuml;/g, "Ï").replace(/&igrave;/g, "ì").replace(/&iacute;/g, "í").replace(/&icirc;/g, "î").replace(/&iuml;/g, "ï").replace(/&Ntilde;/g, "Ñ").replace(/&ntilde;/g, "ñ").replace(/&Ograve;/g, "Ò").replace(/&Oacute;/g, "Ó").replace(/&Ocirc;/g, "Ô").replace(/&Otilde;/g, "Õ").replace(/&Ouml;/g, "Ö").replace(/&ograve;/g, "ò").replace(/&oacute;/g, "ó").replace(/&ocirc;/g, "ô").replace(/&otilde;/g, "õ").replace(/&ouml;/g, "ö").replace(/&Oslash;/g, "Ø").replace(/&oslash;/g, "ø").replace(/&#140;/g, "Œ").replace(/&#156;/g, "œ").replace(/&#138;/g, "Š").replace(/&#154;/g, "š").replace(/&Ugrave;/g, "Ù").replace(/&Uacute;/g, "Ú").replace(/&Ucirc;/g, "Û").replace(/&Uuml;/g, "Ü").replace(/&ugrave;/g, "ù").replace(/&uacute;/g, "ú").replace(/&ucirc;/g, "û").replace(/&uuml;/g, "ü").replace(/&#181;/g, "µ").replace(/&#215;/g, "×").replace(/&Yacute;/g, "Ý").replace(/&#159;/g, "Ÿ").replace(/&yacute;/g, "ý").replace(/&yuml;/g, "ÿ").replace(/&#176;/g, "°").replace(/&#134;/g, "†").replace(/&#135;/g, "‡").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&#177;/g, "±").replace(/&#171;/g, "«").replace(/&#187;/g, "»").replace(/&#191;/g, "¿").replace(/&#161;/g, "¡").replace(/&#183;/g, "·").replace(/&#149;/g, "•").replace(/&#153;/g, "™").replace(/&copy;/g, "©").replace(/&reg;/g, "®").replace(/&#167;/g, "§").replace(/&#182;/g, "¶").replace(/&Alpha;/g, "Α").replace(/&Beta;/g, "Β").replace(/&Gamma;/g, "Γ").replace(/&Delta;/g, "Δ").replace(/&Epsilon;/g, "Ε").replace(/&Zeta;/g, "Ζ").replace(/&Eta;/g, "Η").replace(/&Theta;/g, "Θ").replace(/&Iota;/g, "Ι").replace(/&Kappa;/g, "Κ").replace(/&Lambda;/g, "Λ").replace(/&Mu;/g, "Μ").replace(/&Nu;/g, "Ν").replace(/&Xi;/g, "Ξ").replace(/&Omicron;/g, "Ο").replace(/&Pi;/g, "Π").replace(/&Rho;/g, "Ρ").replace(/&Sigma;/g, "Σ").replace(/&Tau;/g, "Τ").replace(/&Upsilon;/g, "Υ").replace(/&Phi;/g, "Φ").replace(/&Chi;/g, "Χ").replace(/&Psi;/g, "Ψ").replace(/&Omega;/g, "Ω").replace(/&alpha;/g, "α").replace(/&beta;/g, "β").replace(/&gamma;/g, "γ").replace(/&delta;/g, "δ").replace(/&epsilon;/g, "ε").replace(/&zeta;/g, "ζ").replace(/&eta;/g, "η").replace(/&theta;/g, "θ").replace(/&iota;/g, "ι").replace(/&kappa;/g, "κ").replace(/&lambda;/g, "λ").replace(/&mu;/g, "μ").replace(/&nu;/g, "ν").replace(/&xi;/g, "ξ").replace(/&omicron;/g, "ο").replace(/&piρ;/g, "ρ").replace(/&rho;/g, "ς").replace(/&sigmaf;/g, "ς").replace(/&sigma;/g, "σ").replace(/&tau;/g, "τ").replace(/&phi;/g, "φ").replace(/&chi;/g, "χ").replace(/&psi;/g, "ψ").replace(/&omega;/g, "ω").replace(/&bull;/g, "•").replace(/&hellip;/g, "…").replace(/&prime;/g, "′").replace(/&Prime;/g, "″").replace(/&oline;/g, "‾").replace(/&frasl;/g, "⁄").replace(/&weierp;/g, "℘").replace(/&image;/g, "ℑ").replace(/&real;/g, "ℜ").replace(/&trade;/g, "™").replace(/&alefsym;/g, "ℵ").replace(/&larr;/g, "←").replace(/&uarr;/g, "↑").replace(/&rarr;/g, "→").replace(/&darr;/g, "↓").replace(/&barr;/g, "↔").replace(/&crarr;/g, "↵").replace(/&lArr;/g, "⇐").replace(/&uArr;/g, "⇑").replace(/&rArr;/g, "⇒").replace(/&dArr;/g, "⇓").replace(/&hArr;/g, "⇔").replace(/&forall;/g, "∀").replace(/&part;/g, "∂").replace(/&exist;/g, "∃").replace(/&empty;/g, "∅").replace(/&nabla;/g, "∇").replace(/&isin;/g, "∈").replace(/&notin;/g, "∉").replace(/&ni;/g, "∋").replace(/&prod;/g, "∏").replace(/&sum;/g, "∑").replace(/&minus;/g, "−").replace(/&lowast;/g, "∗").replace(/&radic;/g, "√").replace(/&prop;/g, "∝").replace(/&infin;/g, "∞").replace(/&OEig;/g, "Œ").replace(/&oelig;/g, "œ").replace(/&Yuml;/g, "Ÿ").replace(/&spades;/g, "♠").replace(/&clubs;/g, "♣").replace(/&hearts;/g, "♥").replace(/&diams;/g, "♦").replace(/&thetasym;/g, "ϑ").replace(/&upsih;/g, "ϒ").replace(/&piv;/g, "ϖ").replace(/&Scaron;/g, "Š").replace(/&scaron;/g, "š").replace(/&ang;/g, "∠").replace(/&and;/g, "∧").replace(/&or;/g, "∨").replace(/&cap;/g, "∩").replace(/&cup;/g, "∪").replace(/&int;/g, "∫").replace(/&there4;/g, "∴").replace(/&sim;/g, "∼").replace(/&cong;/g, "≅").replace(/&asymp;/g, "≈").replace(/&ne;/g, "≠").replace(/&equiv;/g, "≡").replace(/&le;/g, "≤").replace(/&ge;/g, "≥").replace(/&sub;/g, "⊂").replace(/&sup;/g, "⊃").replace(/&nsub;/g, "⊄").replace(/&sube;/g, "⊆").replace(/&supe;/g, "⊇").replace(/&oplus;/g, "⊕").replace(/&otimes;/g, "⊗").replace(/&perp;/g, "⊥").replace(/&sdot;/g, "⋅").replace(/&lcell;/g, "⌈").replace(/&rcell;/g, "⌉").replace(/&lfloor;/g, "⌊").replace(/&rfloor;/g, "⌋").replace(/&lang;/g, "⟨").replace(/&rang;/g, "⟩").replace(/&loz;/g, "◊").replace(/&#039;/g, "'").replace(/&amp;/g, "&").replace(/&quot;/g, "\"");
}

Używane tak:

let decodedText = removeEncoding("Ich hei&szlig;e David");
console.log(decodedText);

Wydruki: Ich Heiße David

PS to zajęło półtorej godziny.

David Chopin
źródło
0

Aby usunąć elementy HTML * w JavaScript, możesz użyć małej biblioteki html-escaper :npm install html-escaper

import {unescape} from 'html-escaper';

unescape('escaped string');

Lub działajunescape z Lodash lub Underscore , jeśli go używasz.


*) Należy pamiętać, że funkcje te nie obejmują wszystkie podmioty HTML, ale tylko najbardziej popularne, czyli &, <, >, ', ". O przywróceniu znaczenia wszystkie podmioty HTML można użyć he bibliotekę.

Łukasz K
źródło
-1

Używam tego w moim projekcie: zainspirowany innymi odpowiedziami, ale z dodatkowym bezpiecznym parametrem, może być przydatny, gdy masz do czynienia z dekorowanymi postaciami

var decodeEntities=(function(){

    var el=document.createElement('div');
    return function(str, safeEscape){

        if(str && typeof str === 'string'){

            str=str.replace(/\</g, '&lt;');

            el.innerHTML=str;
            if(el.innerText){

                str=el.innerText;
                el.innerText='';
            }
            else if(el.textContent){

                str=el.textContent;
                el.textContent='';
            }

            if(safeEscape)
                str=str.replace(/\</g, '&lt;');
        }
        return str;
    }
})();

I to jest użyteczne jak:

var label='safe <b> character &eacute;ntity</b>';
var safehtml='<div title="'+decodeEntities(label)+'">'+decodeEntities(label, true)+'</div>';
tmx976
źródło
-1

Wszystkie inne odpowiedzi tutaj mają problemy.

Metody document.createElement ('div') (w tym te używające jQuery) wykonują każdy przekazany do nich kod javascript (kwestia bezpieczeństwa), a metoda DOMParser.parseFromString () przycina białe znaki. Oto czyste rozwiązanie javascript, które nie ma żadnego problemu:

function htmlDecode(html) {
    var textarea = document.createElement("textarea");
    html= html.replace(/\r/g, String.fromCharCode(0xe000)); // Replace "\r" with reserved unicode character.
    textarea.innerHTML = html;
    var result = textarea.value;
    return result.replace(new RegExp(String.fromCharCode(0xe000), 'g'), '\r');
}

TextArea jest używana specjalnie w celu uniknięcia wykonywania kodu js. Przechodzi te:

htmlDecode('&lt;&amp;&nbsp;&gt;'); // returns "<& >" with non-breaking space.
htmlDecode('  '); // returns "  "
htmlDecode('<img src="dummy" onerror="alert(\'xss\')">'); // Does not execute alert()
htmlDecode('\r\n') // returns "\r\n", doesn't lose the \r like other solutions.
EricP
źródło
1
Nie, użycie innego tagu nie rozwiązuje problemu. To wciąż jest luka w zabezpieczeniach XSS, spróbuj htmlDecode("</textarea><img src=x onerror=alert(1)>"). Opublikowałeś to po tym, jak już wskazałem tę kwestię w odpowiedzi Sergio Belevskij.
Wladimir Palant
Nie jestem w stanie odtworzyć opisanego przez Ciebie problemu. Mam twój kod w tym JsFiddle i żaden alert nie wyświetla się podczas pracy. jsfiddle.net/edsjt15g/1 Czy możesz rzucić okiem? Jakiej przeglądarki używasz?
EricP
2
Używam przeglądarki Firefox. Chrome rzeczywiście radzi sobie z tym scenariuszem inaczej, więc kod nie jest wykonywany - nie jest to jednak coś, na czym powinieneś polegać.
Wladimir Palant
-1
var encodedStr = 'hello &amp; world';

var parser = new DOMParser;
var dom = parser.parseFromString(
    '<!doctype html><body>' + encodedStr,
    'text/html');
var decodedString = dom.body.textContent;

console.log(decodedString);
jagjeet
źródło
@Wladimir Palant (autor AdBlock Plus) udzielił odpowiedzi DOMParser już 4 lata wcześniej. Czy przeczytałeś poprzednie odpowiedzi przed wysłaniem swoich?
Dan Dascalescu
-7

Istnieje wariant, który w 80% jest tak produktywny, jak odpowiedzi na samej górze.

Zobacz test porównawczy: https://jsperf.com/decode-html12345678/1

test wydajności

console.log(decodeEntities('test: &gt'));

function decodeEntities(str) {
  // this prevents any overhead from creating the object each time
  const el = decodeEntities.element || document.createElement('textarea')

  // strip script/html tags
  el.innerHTML = str
    .replace(/<script[^>]*>([\S\s]*?)<\/script>/gmi, '')
    .replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, '');

  return el.value;
}

Jeśli chcesz zostawić tagi, usuń dwa .replace(...)wywołania (możesz zostawić pierwsze, jeśli nie potrzebujesz skryptów).

Илья Зеленько
źródło
6
Gratulacje, udało ci się ukryć lukę za pomocą fałszywej logiki sanityzacji, a wszystko to po to, aby wygrać wydajność, która nie ma znaczenia w praktyce. Spróbuj zadzwonić decodeEntities("</textarea '><img src=x onerror=alert(1) \">")w przeglądarce Firefox. Przestań próbować oczyścić kod HTML za pomocą wyrażeń regularnych.
Wladimir Palant