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?

Sztuka
źródło
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ść.

Jeremy Banks
źródło
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.

Łukasz
źródło
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.

Mark Amery
źródło
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.

WaiKit Kung
źródło
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
17

element.innerText robi to samo.

avg_joe
źródło
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'

Jestem L.
źródło
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.

cslotty
źródło
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>

Jason Williams
źródło
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
Infoglaze.com
źródło
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

Peter Brandt
źródło
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'));
Ninh Pham
źródło
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).

Andrew Hodgkinson
źródło
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.

Digexart
źródło
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.