Odpowiednik HtmlSpecialChars w JavaScript?

167

Najwyraźniej jest to trudniejsze do znalezienia, niż myślałem. I to nawet jest takie proste ...

Czy istnieje odpowiednik funkcji htmlspecialchars PHP wbudowanej w Javascript? Wiem, że jest to dość łatwe do zaimplementowania samodzielnie, ale korzystanie z wbudowanej funkcji, jeśli jest dostępna, jest po prostu przyjemniejsze.

Dla tych, którzy nie znają PHP, htmlspecialchars tłumaczy takie rzeczy jak <htmltag/>na&lt;htmltag/&gt;

Wiem o tym escape()i encodeURI()nie działam w ten sposób.

Bart van Heukelom
źródło
php ma kilka naprawdę dobrych narzędzi, var_dump, print_r, htmlspecialchars itp. Niestety podejrzewam, że to nie to samo z js. js jest tak słaba. Szybkim sposobem, aby zobaczyć, że nadchodzi jakiś nieoczekiwany (i niewidoczny w polu alarmowym) ciąg znaków, jest ostrzeżenie o długości łańcucha zamiast jego końca.
Melsi
Możliwy duplikat Escaping HTML strings z jQuery
nhahtdh
Zobacz stackoverflow.com/a/12034334/8804293 , ma świetną odpowiedź
Elijah Mock

Odpowiedzi:

330

Wystąpił problem z kodem rozwiązania - będzie on zmieniał się tylko przed pierwszym wystąpieniem każdego znaku specjalnego. Na przykład:

escapeHtml('Kip\'s <b>evil</b> "test" code\'s here');
Actual:   Kip&#039;s &lt;b&gt;evil</b> &quot;test" code's here
Expected: Kip&#039;s &lt;b&gt;evil&lt;/b&gt; &quot;test&quot; code&#039;s here

Oto kod, który działa poprawnie:

function escapeHtml(text) {
  return text
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
}

Aktualizacja

Poniższy kod da identyczne wyniki jak powyższy, ale działa lepiej, szczególnie w przypadku dużych bloków tekstu (dzięki jbo5112 ).

function escapeHtml(text) {
  var map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };
  
  return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}
Wyrko
źródło
5
fajną rzeczą w tej funkcji jest to, że działa w node.js, który domyślnie nie ma
domeny
6
Szybsze jest użycie funkcji pojedynczego zastępowania i mapowania, a pojedyncza zamiana skaluje się znacznie lepiej. ( jsperf.com/escape-html-special-chars/11 )
jbo5112
1
@ jbo5112 dobra uwaga, nie zdawałem sobie sprawy, że JS zezwala na wywołania zwrotne do wymiany. Ten kod jest jednak łatwiejszy do zrozumienia i wątpię, czy skrócenie czasu o kilka milisekund od escapeHtml () zrobi różnicę, chyba że z jakiegoś powodu wywołasz go setki razy z rzędu.
Kip
Spowoduje to zniekształcenie adresów URL w tekście, co spowoduje, że będą one bezużyteczne dla wtyczek, takich jak Autolinker.js . Czy jest jakiś sposób, jak do tego podejść?
Radek Matěj
4
@ RadekMatěj Nawet w tym przypadku jest całkowicie poprawne (preferowałbym argumentowanie), aby oba znaki ampersandów były zakodowane w dokumencie HTML. Nadal uważałbym to za błąd wtyczki.
Kip
31

To jest kodowanie HTML. Nie ma do tego natywnej funkcji javascript, ale możesz wygooglować i uzyskać kilka ładnie wykonanych.

Np. Http://sanzon.wordpress.com/2008/05/01/neat-little-html-encoding-trick-in-javascript/

EDYCJA:
Oto, co przetestowałem:

var div = document.createElement('div');
  var text = document.createTextNode('<htmltag/>');
  div.appendChild(text);
  console.log(div.innerHTML);

Wynik: &lt;htmltag/&gt;

okw
źródło
Szkoda, będę musiał wtedy po prostu użyć funkcji niestandardowej.
Bart van Heukelom
Możesz wypróbować metodę w linku, który zamieściłem w moim poście. Całkiem zgrabna koncepcja.
okw
@okw: Ok, najpierw utworzyłeś link do tego: yuki-onna.co.uk/html/encode.html, który robi dokładnie to, co encodeURIComponentrobi, a nie to, o co prosił OP. Więc czy możesz edytować? Nie mogę cofnąć mojej -1.
Crescent Fresh
Tak, kod tej strony wygląda logicznie, ale nie testowałem tego. Nowy link jednak działa, sam to zweryfikowałem. Już jakiś czas temu zaktualizowałem post.
okw
@BeauCielBleu: Nie. Jedyne tworzone węzły to pojedynczy divelement i węzeł tekstowy. Utworzenie węzła tekstowego z tekstem `<img src = bogus onerror = alert (1337)>` spowoduje po prostu utworzenie węzła tekstowego, a nie imgelementu.
Tim Down
26

Warto przeczytać: http://bigdingus.com/2007/12/29/html-escaping-in-javascript/

escapeHTML: (function() {
 var MAP = {
   '&': '&amp;',
   '<': '&lt;',
   '>': '&gt;',
   '"': '&#34;',
   "'": '&#39;'
 };
  var repl = function(c) { return MAP[c]; };
  return function(s) {
    return s.replace(/[&<>'"]/g, repl);
  };
})()

Uwaga : uruchom to tylko raz. I nie uruchamiaj go na już zakodowanych łańcuchach, np. &amp;Staje się&amp;amp;

Chris Jacob
źródło
3
To powinna być przyjęta i najwyżej oceniona odpowiedź. Nie jestem pewien, dlaczego nie miał głosów. To test porównawczy jako najszybszy z długim (326 KB wynik wyszukiwania Google) i krótkim ciągiem wejściowym w jsperf ( jsperf.com/escape-html-special-chars/11 ). Prosimy o głosowanie.
jbo5112
Jaka jest różnica między tą a odpowiedzią, która uzyskała najwięcej głosów? Dlaczego dodatkowa funkcja wewnętrzna? Wyjaśnienie mogłoby pomóc użytkownikom lepiej zrozumieć
Kosem
19

Z jQuery może wyglądać tak:

var escapedValue = $('<div/>').text(value).html();

Z pokrewnego pytania Escapowanie ciągów HTML za pomocą jQuery

Jak wspomniano w komentarzu, dla tej implementacji podwójne cudzysłowy i pojedyncze cudzysłowy są pozostawione bez zmian. Oznacza to, że to rozwiązanie nie powinno być używane, jeśli chcesz utworzyć atrybut elementu jako nieprzetworzony ciąg html.

Alexander Yanovets
źródło
2
masz pomysł, czy jest jakiś narzut - dodanie obiektu fikcyjnego do DOM?
Kip
i czy są jakieś inne zalety (powiedzmy, czy masz znaki Unicode czy coś)?
Kip
4
Coś, co znalazłem w tym: podwójne cudzysłowy i pojedyncze cudzysłowy są pozostawione bez zmian. To sprawia, że ​​jest to problematyczne, jeśli chcesz go użyć w wartości atrybutu.
Kip
1
W przypadku małych fragmentów tekstu trwa to 30 razy dłużej niż uruchomienie wszystkich zamian. Jednak skaluje się lepiej. Z czymś tak gigantycznym jak strona wyników wyszukiwania Google (326KB), jest o 25-30% szybszy niż zastępowanie lub robienie tego w prostym javascript. Jednak wszyscy konsekwentnie przegrywają z pojedynczym zastąpieniem i funkcją mapowania.
jbo5112
4
jak ludzie głosują na tę odpowiedź: odpowiedź ma jquery: +1 - NIE unika pojedynczych i podwójnych cudzysłowów: ummmm .. (drapanie głowy) .. +1. <!-- Caps rage begin --> Ta odpowiedź powinna mieć wynik UJEMNY, ponieważ NAWET NIE BLISKO ODPOWIEDZI NA PYTANIE „Odpowiednik HtmlSpecialChars”. <!-- Caps rage end -->cytaty-jezusa-chrystusa-i-innych-bóstw-to-nie-ucieka-. OMG wy jquery people.
Sharky
19

Oto funkcja umożliwiająca ucieczkę z HTML:

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

I odszyfrować:

function decodeHtml(str)
{
    var map =
    {
        '&amp;': '&',
        '&lt;': '<',
        '&gt;': '>',
        '&quot;': '"',
        '&#039;': "'"
    };
    return str.replace(/&amp;|&lt;|&gt;|&quot;|&#039;/g, function(m) {return map[m];});
}
Dan Bray
źródło
6

Underscore.js udostępnia do tego funkcję:

_.escape(string)

Ucieka ciąg znaków do wstawienia do HTML, zastępując znaki &, <,>, "i '.

http://underscorejs.org/#escape

Nie jest to wbudowana funkcja JavaScript, ale jeśli już używasz podkreślenia, jest to lepsza alternatywa niż pisanie własnej funkcji, jeśli twoje ciągi do konwersji nie są zbyt duże.

mer10z_tech
źródło
5

Jeszcze innym podejściem jest całkowite zrezygnowanie z mapowania znaków i zamiast tego przekonwertowanie wszystkich niechcianych znaków na odpowiadające im odnośniki numeryczne, np .:

function escapeHtml(raw) {
    return raw.replace(/[&<>"']/g, function onReplace(match) {
        return '&#' + match.charCodeAt(0) + ';';
    });
}

Zwróć uwagę, że określone wyrażenie RegEx obsługuje tylko określone znaki, z których OP chciał uciec, ale w zależności od kontekstu, w którym zostanie użyty kod HTML ze ucieczką, te znaki mogą nie być wystarczające. Artykuł Ryana Grove'a Jest więcej znaków ucieczki HTML niż &, <,> i " to dobra lektura na ten temat. W zależności od kontekstu, poniższe wyrażenie regularne może być bardzo potrzebne, aby uniknąć wstrzyknięcia XSS:

var regex = /[&<>"'` !@$%()=+{}[\]]/g
Fredric
źródło
3
String.prototype.escapeHTML = function() {
        return this.replace(/&/g, "&amp;")
                   .replace(/</g, "&lt;")
                   .replace(/>/g, "&gt;")
                   .replace(/"/g, "&quot;")
                   .replace(/'/g, "&#039;");
    }

próbka:

var toto = "test<br>";
alert(toto.escapeHTML());
patrick
źródło
3

Prawdopodobnie nie potrzebujesz takiej funkcji. Ponieważ Twój kod znajduje się już w przeglądarce *, możesz uzyskać bezpośredni dostęp do DOM zamiast generować i kodować HTML, który będzie musiał zostać zdekodowany wstecz przez przeglądarkę, aby faktycznie był używany.

Użyj innerTextwłaściwości, aby bezpiecznie i znacznie szybciej wstawić zwykły tekst do DOM, niż przy użyciu którejkolwiek z przedstawionych funkcji ucieczki. Nawet szybciej niż przypisanie statycznego wstępnie zakodowanego ciągu doinnerHTML .

Służy classListdo edycji klas, datasetustawiania data-atrybutów isetAttribute innych.

Wszystkie te poradzą sobie z ucieczką za Ciebie. Mówiąc dokładniej, nie ma potrzeby ucieczki i żadne kodowanie nie będzie wykonywane pod spodem **, ponieważ pracujesz nad HTML, tekstową reprezentacją DOM.

// use existing element
var author = 'John "Superman" Doe <[email protected]>';
var el = document.getElementById('first');
el.dataset.author = author;
el.textContent = 'Author: '+author;

// or create a new element
var a = document.createElement('a');
a.classList.add('important');
a.href = '/search?q=term+"exact"&n=50';
a.textContent = 'Search for "exact" term';
document.body.appendChild(a);

// actual HTML code
console.log(el.outerHTML);
console.log(a.outerHTML);
.important { color: red; }
<div id="first"></div>

* Ta odpowiedź nie jest przeznaczona dla użytkowników JavaScript po stronie serwera (Node.js itp. )

** Chyba że później wyraźnie przekonwertujesz go na rzeczywisty HTML. Np. Poprzez dostęp innerHTML- tak się dzieje, gdy uruchamiasz $('<div/>').text(value).html();sugerowane w innych odpowiedziach. Więc jeśli Twoim ostatecznym celem jest wstawienie pewnych danych do dokumentu, robiąc to w ten sposób, wykonasz tę pracę dwukrotnie. Możesz również zobaczyć, że w wynikowym HTML nie wszystko jest zakodowane, a jedynie minimum potrzebne do poprawności. Odbywa się to w zależności od kontekstu, dlatego ta metoda jQuery nie koduje cudzysłowów i dlatego nie powinna być używana jako eskaper ogólnego przeznaczenia. Znakowanie cudzysłowami jest potrzebne, gdy konstruujesz kod HTML jako ciąg znaków z niezaufanymi lub zawierającymi cytaty danymi w miejscu wartości atrybutu. Jeśli używasz DOM API, nie musisz w ogóle martwić się ucieczką.

użytkownik
źródło
Dzięki za to! Bardzo długo szukałem tak prostego rozwiązania. Jedną z ważnych rzeczy, które odkryłem, jest to, że jeśli twój tekst zawiera znaki nowej linii, będziesz musiał albo zamienić je na znaki el.textContent = str; el.innerHTML = el.innerHTML.replace(/\n/g, '<br>')końca linii HTML (coś w rodzaju ), albo ustawić white-spacewłaściwość CSS na prelubpre-wrap
stellatedHexahedron
@stellatedHexahedron, dzięki za poruszenie tego problemu. Zmieniłem odpowiedź na polecanie innerTextzamiast textContent. Chociaż jest nieco wolniejszy i ma kilka innych różnic podczas odczytywania właściwości, jest bardziej intuicyjny, ponieważ <br>zastępuje automatycznie podczas przypisywania do niej.
użytkownik
2

W przypadku użytkowników Node.JS (lub użytkowników korzystających ze środowiska uruchomieniowego Jade w przeglądarce) można skorzystać z funkcji ucieczki Jade.

require('jade').runtime.escape(...);

Nie ma sensu pisać tego samemu, jeśli ktoś inny go utrzymuje. :)

BMiner
źródło
1

Rozwijam trochę odpowiedź okw.

Możesz użyć do tego funkcji DOM przeglądarki.

var utils = {
    dummy: document.createElement('div'),
    escapeHTML: function(s) {
        this.dummy.textContent = s
        return this.dummy.innerHTML
    }
}

utils.escapeHTML('<escapeThis>&')

To wraca &lt;escapeThis&gt;&amp;

Używa standardowej funkcji createElementdo stworzenia niewidocznego elementu, a następnie używa funkcji textContentdo ustawienia dowolnego ciągu jako jego zawartości, a następnie innerHTMLdo pobrania zawartości w reprezentacji HTML.

Jonas Eberle
źródło
0
function htmlspecialchars(str) {
 if (typeof(str) == "string") {
  str = str.replace(/&/g, "&amp;"); /* must do &amp; first */
  str = str.replace(/"/g, "&quot;");
  str = str.replace(/'/g, "&#039;");
  str = str.replace(/</g, "&lt;");
  str = str.replace(/>/g, "&gt;");
  }
 return str;
 }

źródło
0

Mam nadzieję, że to wygra wyścig ze względu na swoje osiągi i co najważniejsze, nie jest to łańcuchowa logika używająca .replace ('&', '&'). Replace ('<', '<') ...

var mapObj = {
   '&':"&amp;",
   '<':"&lt;",
   '>':"&gt;",
   '"':"&quot;",
   '\'':"&#039;"
};
var re = new RegExp(Object.keys(mapObj).join("|"),"gi");

function escapeHtml(str) 
{   
    return str.replace(re, function(matched)
    {
        return mapObj[matched.toLowerCase()];
    });
}

console.log('<script type="text/javascript">alert('Hello World');</script>');
console.log(escapeHtml('<script type="text/javascript">alert('Hello World');</script>'));
Przewiewny
źródło
0

Odwrócony:

function decodeHtml(text) {
    return text
        .replace(/&amp;/g, '&')
        .replace(/&lt;/ , '<')
        .replace(/&gt;/, '>')
        .replace(/&quot;/g,'"')
        .replace(/&#039;/g,"'");
}
Gleb Dolzikov
źródło
Pytanie nie dotyczy sposobu dekodowania jednostek. To jest odwrotność tego, o co chodzi w pytaniu.
Quentin
Spowoduje to zastąpienie tylko pierwszej instancji &lt;i &gr;w ciąg.
Quentin
Spowoduje to zdekodowanie tylko pięciu znaków, które (poza dokumentami nie obsługującymi Unicode) muszą być chronione przed ucieczką, nie zdekoduje znaków , które mogą zostać pominięte.
Quentin
Nie uwzględnia to reguł określających, kiedy średnik jest opcjonalny.
Quentin
Jeśli kod HTML mówi To write a greater than sign in HTML type &amp;gt;:, wyświetli się niepoprawnie >zamiast&gt;
Quentin
0

OWASP zaleca, aby „[e] xz wyjątkiem znaków alfanumerycznych, [powinieneś] zmienić znaczenie wszystkich znaków z wartościami ASCII mniejszymi niż 256 z&#xHH; formatem (lub nazwaną jednostką, jeśli jest dostępna), aby zapobiec wyłączeniu [an] atrybutu”.

Oto funkcja, która to robi, na przykładzie użycia:

function escapeHTML(unsafe) {
  return unsafe.replace(
    /[\u0000-\u002F]|[\u003A-\u0040]|[\u005B-\u00FF]/g,
    c => '&#' + ('000' + c.charCodeAt(0)).substr(-4, 4) + ';'
  )
}
document.querySelector('div').innerHTML =
  '<span class=' +
  escapeHTML('this should break it! " | / % * + , - / ; < = > ^') +
  '>' +
  escapeHTML('<script>alert("inspect the attributes")\u003C/script>') +
  '</span>'
<div></div>

ADJenks
źródło
-1
function htmlEscape(str){
    return str.replace(/[&<>'"]/g,x=>'&#'+x.charCodeAt(0)+';')
}

W rozwiązaniu tym wykorzystuje się numeryczny kod znaków, <zastępując np &#60;..

Chociaż jego wydajność jest nieco gorsza od rozwiązania wykorzystującego mapę , ma zalety:

  • Nie zależy od biblioteki ani DOM
  • Całkiem łatwe do zapamiętania (nie musisz zapamiętywać 5 znaków ucieczki HTML)
  • Mały kod
  • Dość szybko (nadal jest szybszy niż wymiana 5 łańcuchów)
user202729
źródło