Dekoduj i wzmacniaj powrót do i w JavaScript


229

Mam ciągi jak

var str = 'One & two & three';

renderowane do HTML przez serwer WWW. Muszę przekształcić te ciągi

'One & two & three'

Obecnie to właśnie robię (przy pomocy jQuery):

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

Mam jednak niepokojące wrażenie, że robię to źle. próbowałem

unescape("&")

ale wydaje się, że to nie działa, podobnie jak decodeURI / decodeURIComponent.

Czy istnieją inne, bardziej rodzime i eleganckie sposoby na zrobienie tego?


Ogromna funkcja zawarta w tym artykule wydaje się działać dobrze: blogs.msdn.com/b/aoakley/archive/2003/11/12/49645.aspx Nie sądzę, że jest to najmądrzejsze rozwiązanie, ale działa.
— Matias

1
Ponieważ ciągi znaków zawierające elementy HTML są czymś innym niż ciągi kodowaneescape d lub URI , funkcje te nie będą działać.
— Marcel Korpel,

1
@Matias zauważa, że ​​nowe nazwane byty zostały dodane do HTML (np. Poprzez specyfikację HTML 5), ponieważ ta funkcja została stworzona w 2003 roku - na przykład nie rozpoznaje 𝕫. Jest to problem związany ze zmieniającą się specyfikacją; dlatego powinieneś wybrać narzędzie, które faktycznie jest utrzymywane, aby je rozwiązać.
— Mark Amery

1
@ MarkAmery tak, całkowicie się zgadzam! To miłe doświadczenie wrócić do tych pytań po kilku latach, dzięki!
— Matias

Odpowiedzi:


104

Bardziej nowoczesną opcją interpretacji HTML (tekst i inne) z JavaScript jest obsługa HTML w DOMParserAPI ( patrz tutaj w MDN ). Pozwala to na użycie natywnego parsera HTML przeglądarki do konwersji ciągu znaków na dokument HTML. Jest obsługiwany w nowych wersjach wszystkich głównych przeglądarek od końca 2014 roku.

Jeśli chcemy po prostu odkodować część tekstu, możemy umieścić go jako jedyną treść w treści dokumentu, przeanalizować dokument i wyciągnąć z niego .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);

Widzimy w specyfikacji roboczej,DOMParser że JavaScript nie jest włączony dla analizowanego dokumentu, więc możemy wykonać tę konwersję tekstu bez obaw związanych z bezpieczeństwem.

parseFromString(str, type)Metoda musi uruchomić te czynności, w zależności od typu :

  • "text/html"

    Analizuj str za pomocą HTML parseri zwraca nowo utworzony Document.

    Flaga skryptów musi być ustawiona na „wyłączone”.

    UWAGA

    scriptelementy zostają oznaczone jako niewykonalne, a zawartość noscriptparsowana jako znaczniki.

To wykracza poza zakres tego pytania, ale pamiętaj, że jeśli weźmiesz parsowane węzły DOM (nie tylko ich treść tekstową) i przeniesiesz je do DOM dokumentu na żywo, możliwe, że ich skrypty zostaną ponownie włączone, i może mieć obawy dotyczące bezpieczeństwa. Nie badałem tego, więc zachowaj ostrożność.


5
jakaś alternatywa dla NodeJ?
— coderInrRain

284

Czy musisz zdekodować wszystkie zakodowane jednostki HTML, czy tylko &amp;sam?

Jeśli potrzebujesz tylko obsługiwać, &amp;możesz to zrobić:

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

Jeśli musisz zdekodować wszystkie jednostki HTML, możesz to zrobić bez jQuery:

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

Zwróć uwagę na komentarze Marka poniżej, które podkreślają luki w zabezpieczeniach we wcześniejszej wersji tej odpowiedzi i zalecają stosowanie textareazamiast divłagodzenia potencjalnych luk w zabezpieczeniach XSS. Luki te występują niezależnie od tego, czy używasz jQuery, czy zwykłego JavaScript.


16
Strzec się! Jest to potencjalnie niepewne. Jeśli encoded='<img src="bla" onerror="alert(1)">'następnie powyższy fragment pokaże alert. Oznacza to, że jeśli zakodowany tekst pochodzi z danych wprowadzonych przez użytkownika, dekodowanie go za pomocą tego fragmentu kodu może stanowić lukę w zabezpieczeniach XSS.
— Mark Amery

@ MarkAmery Nie jestem ekspertem od bezpieczeństwa, ale wygląda na to, że jeśli natychmiast otrzymasz div nullpo otrzymaniu tekstu, alert w img nie zostanie wyzwolony
— jsfiddle.net/Mottie/gaBeb/128

4
@Mottie pamiętaj, która przeglądarka działała dla Ciebie, ale alert(1)nadal działa dla mnie w Chrome na OS X. Jeśli chcesz bezpiecznego wariantu tego hacka, spróbuj użyćtextarea .
— Mark Amery

+1 za proste wyrażenie regularne zamień alternatywę tylko na jeden rodzaj jednostki HTML. Użyj tego, jeśli oczekujesz interpolacji danych HTML z, powiedzmy, aplikacji kolby pythonowej na szablon.
— OzzyTheGiant

Jak to zrobić na serwerze Node?
— Mohammad Kermani

44

Matthias Bynens ma bibliotekę do tego: https://github.com/mathiasbynens/he

Przykład:

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

Sugeruję faworyzowanie go w stosunku do hacków polegających na ustawianiu zawartości HTML elementu, a następnie ponownym przeczytaniu jego zawartości tekstowej. Takie podejścia mogą działać, ale są zwodniczo niebezpieczne i stwarzają możliwości XSS, jeśli są stosowane przy niezaufanym wkładzie użytkownika.

Jeśli naprawdę nie możesz znieść ładowania do biblioteki, możesz skorzystać z textareahacka opisanego w tej odpowiedzi na prawie zduplikowane pytanie, które, w przeciwieństwie do różnych podobnych podejść, które zostały zasugerowane, nie ma dziur w zabezpieczeniach, o których wiem:

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

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

Ale zwróć uwagę na kwestie bezpieczeństwa, wpływające na podobne podejścia do tego, które wymienię w powiązanej odpowiedzi! Takie podejście jest włamaniem, a przyszłe zmiany dopuszczalnej zawartości textarea(lub błędów w określonych przeglądarkach) mogą doprowadzić do tego, że kod, który się na niej opiera, nagle ma dziurę w XSS.


Biblioteka Matthiasa Bynensa hejest absolutnie świetna! Dziękuję bardzo za rekomendację!
— 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
    };
})();

Pochodzi z kodu źródłowego ExtJS.


4
-1; to nie obsługuje większości nazwanych podmiotów. Na przykład htmlEnDecode.htmlDecode('&euro;')powinien zwrócić '€', ale zamiast tego zwraca '&euro;'.
— Mark Amery


15

Możesz użyć funkcji Lodash unescape / escape https://lodash.com/docs/4.17.5#unescape

import unescape from 'lodash/unescape';

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

str stanie się 'fred, barney, & pebbles'


1
prawdopodobnie lepiej zrobić „import _unescape z 'lodash / unescape';” więc nie koliduje z przestarzałą funkcją javascript o tej samej nazwie:
— unescape

14

Jeśli tego szukasz, tak jak ja - tymczasem istnieje miła i bezpieczna metoda JQuery.

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

Możesz np. wpisz to w konsoli:

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

Tak więc $ .parseHTML (x) zwraca tablicę, a jeśli masz znaczniki HTML w tekście, długość tablicy będzie większa niż 1.


Działa idealnie dla mnie, właśnie tego szukałem, dziękuję.
— Jonathan Nielsen

1
Jeśli xma wartość <script>alert('hello');</script>powyższą, nastąpi awaria. W bieżącym jQuery tak naprawdę nie będzie próbował uruchomić skryptu, ale [0]da wynik, undefinedwięc wywołanie textContentnie powiedzie się, a skrypt się na nim zatrzyma. $('<div />').html(x).text();wygląda bezpieczniej - przez gist.github.com/jmblog/3222899
— Andrew Hodgkinson,

@AndrewHodgkinson tak, ale pytanie brzmiało: „Dekoduj i wróć do & w JavaScript” - więc najpierw przetestuj zawartość x lub upewnij się, że używasz jej tylko we właściwych przypadkach.
— cslotty

Naprawdę nie rozumiem, jak to się dzieje. Powyższy kod działa we wszystkich przypadkach. A jak dokładnie „upewnisz się”, że wartość x wymaga naprawy? A co jeśli powyższy przykład skryptu zaalarmował „& amp;” żeby naprawdę potrzebował korekty? Nie mamy pojęcia, skąd pochodzą ciągi PO, więc należy wziąć pod uwagę złośliwe dane wejściowe.
— Andrew Hodgkinson,

@AndrewHodgkinson Podoba mi się twoje rozważanie, ale nie o to tu chodzi. Jednak możesz odpowiedzieć na to pytanie. Myślę, że możesz usunąć tagi skryptu, np.
— cslotty

8

jQuery koduje i dekoduje dla ciebie. Musisz jednak użyć tagu textarea, a nie 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>


2
-1, ponieważ istnieje (zaskakująca) luka w zabezpieczeniach dla starych wersji jQuery, z których niektóre prawdopodobnie nadal mają znaczną bazę użytkowników - te wersje wykryją i jawnie ocenią skrypty w przekazanym HTML .html(). Dlatego nawet użycie textareanie wystarcza do zapewnienia bezpieczeństwa; Sugeruję, aby nie używać jQuery do tego zadania i pisać równoważnego kodu za pomocą zwykłego API DOM . (Tak, to stare zachowanie jQuery jest szalone i okropne.)
— Mark Amery

Dziękuję za zwrócenie na to uwagi. Pytanie to nie zawiera jednak wymogu sprawdzania zastrzyku skryptu. Pytanie dotyczy w szczególności html renderowanego przez serwer WWW. Zawartość HTML zapisana na serwerze sieciowym powinna prawdopodobnie zostać sprawdzona pod kątem wstrzyknięcia skryptu przed zapisaniem.
— Jason Williams

4

Najpierw stwórz <span id="decodeIt" style="display:none;"></span>gdzieś w ciele

Następnie przypisz ciąg do zdekodowania jako innerHTML do tego:

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

Wreszcie,

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

Oto ogólny kod:

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

1
-1; jest to niebezpiecznie niepewne w użyciu przy niezaufanym wejściu. Na przykład zastanów się, co się stanie, jeśli stringtodecodezawiera coś takiego <script>alert(1)</script>.
— Mark Amery

2

javascript, który łapie typowe:

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

jest to odwrotność https://stackoverflow.com/a/4835406/2738039


Jeśli użyjesz map[c] || ''nierozpoznanych, nie będą wyświetlane jakoundefined
— Eldelshell

Bardzo ograniczony zasięg; -1.
— Mark Amery

2
+1, więcej tounescapeHtml(str){ var map = {amp: '&', lt: '<', le: '≤', gt: '>', ge: '≥', quot: '"', '#039': "'"} return str.replace(/&([^;]+);/g, (m, c) => map[c]|| '') }
— Trần Quốc Hoài nowy

Zasięg ręczny. Niepolecane.
— Sergio A.

2

Dla facetów z jednej linii:

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

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

2

Pytanie nie określa pochodzenia, xale warto bronić, jeśli to możliwe, przed złośliwymi (lub po prostu nieoczekiwanymi przez naszą własną aplikację) danymi wejściowymi. Załóżmy na przykład, że xma wartość &amp; <script>alert('hello');</script>. Bezpiecznym i prostym sposobem radzenia sobie z tym w jQuery jest:

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

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

Znaleziono za pośrednictwem https://gist.github.com/jmblog/3222899 . Nie widzę wielu powodów, aby unikać korzystania z tego rozwiązania, ponieważ jest ono co najmniej tak krótkie, jeśli nie krótsze niż niektóre alternatywy i zapewnia ochronę przed XSS.

(Pierwotnie zamieściłem to jako komentarz, ale dodaję to jako odpowiedź, ponieważ poprosił mnie o to kolejny komentarz w tym samym wątku).


1

Próbowałem wszystkiego, aby usunąć & z tablicy JSON. Żaden z powyższych przykładów, ale https://stackoverflow.com/users/2030321/chris dał świetne rozwiązanie, które doprowadziło mnie do rozwiązania mojego problemu.

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

Nie korzystałem, ponieważ nie rozumiałem, jak wstawić go do okna modalnego, które wciągało dane JSON do tablicy, ale spróbowałem tego na podstawie przykładu i zadziałało:

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

Podoba mi się, ponieważ był prosty i działa, ale nie jestem pewien, dlaczego nie jest szeroko stosowany. Szukano hi & low, aby znaleźć proste rozwiązanie. Nadal szukam zrozumienia tej składni i jeśli istnieje jakiekolwiek ryzyko z jej użyciem. Nic jeszcze nie znalazłem.


Twoja pierwsza propozycja jest nieco trudna, ale działa dobrze bez większego wysiłku. Drugi natomiast wykorzystuje jedynie brutalną siłę do dekodowania znaków; oznacza to, że osiągnięcie pełnej funkcji dekodowania może wymagać wiele wysiłku i czasu. Dlatego nikt nie używa tej metody do rozwiązania problemu OP.
— Sergio A.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.