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.