Unescape HTML сущности в JavaScript?
У меня есть некоторый код Javascript, который взаимодействует с бэкэндом XML-RPC. XML-RPC возвращает строки в форме:
<img src='myimage.jpg'>
Однако когда я использую Javascript для вставки строк в HTML, они отображаются буквально. Я не вижу изображения, я буквально вижу строку:
<img src='myimage.jpg'>
Я предполагаю, что HTML экранируется по каналу XML-RPC.
Как я могу удалить строку в Javascript? Я попробовал методы на этой странице, но безуспешно: http://paulschreiber.com/blog/2008/09/20/javascript-how-to-unescape-html-entities/
Каковы другие способы диагностики проблемы?
23 ответа
Я использую следующий метод:
function htmlDecode(input){
var e = document.createElement('div');
e.innerHTML = input;
// handle case of empty input
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}
htmlDecode("<img src='myimage.jpg'>");
// returns "<img src='myimage.jpg'>"
По сути, я создаю элемент DOM программно, назначаю закодированный HTML-код его innerHTML и извлекаю nodeValue из текстового узла, созданного при вставке innerHTML. Поскольку он просто создает элемент, но никогда не добавляет его, HTML-код сайта не изменяется.
Он будет работать в кросс-браузерном режиме (включая старые браузеры) и принимать все объекты символов HTML.
РЕДАКТИРОВАТЬ: старая версия этого кода не работала на IE с пустыми вводами, как показано здесь на jsFiddle (представление в IE). Версия выше работает со всеми входами.
ОБНОВЛЕНИЕ: кажется, это не работает с большой строкой, и это также вводит уязвимость безопасности, см. Комментарии.
Большинство ответов, приведенных здесь, имеют огромный недостаток: если строка, которую вы пытаетесь преобразовать, не является доверенной, в результате вы получите уязвимость для межсайтового скриптинга (XSS). Для функции в принятом ответе учтите следующее:
htmlDecode("<img src='dummy' onerror='alert(/xss/)'>");
Здесь строка содержит неэкранированный HTML-тег, поэтому вместо декодирования чего-либо htmlDecode
Функция на самом деле будет запускать код JavaScript, указанный внутри строки.
Этого можно избежать, используя DOMParser, который поддерживается во всех современных браузерах:
function htmlDecode(input)
{
var doc = new DOMParser().parseFromString(input, "text/html");
return doc.documentElement.textContent;
}
// This returns "<img src='myimage.jpg'>"
htmlDecode("<img src='myimage.jpg'>");
// This returns ""
htmlDecode("<img src='dummy' onerror='alert(/xss/)'>");
Эта функция гарантированно не запускает какой-либо код JavaScript как побочный эффект. Любые HTML-теги будут игнорироваться, будет возвращен только текстовый контент.
Примечание о совместимости: анализ HTML с DOMParser
требуется как минимум Chrome 30, Firefox 12, Opera 17, Internet Explorer 10, Safari 7.1 или Microsoft Edge. Таким образом, все браузеры без поддержки уже прошли EOL, и по состоянию на 2017 год единственными, которые все еще можно увидеть в дикой природе, иногда являются старые версии Internet Explorer и Safari (обычно их все еще недостаточно, чтобы беспокоиться).
Если вы используете jQuery:
function htmlDecode(value){
return $('<div/>').html(value).text();
}
В противном случае используйте объект кодирования Strictly Software, который имеет превосходный htmlDecode()
функция.
Хитрость заключается в том, чтобы использовать возможности браузера для декодирования специальных символов HTML, но не позволять браузеру выполнять результаты, как если бы это был фактический HTML... Эта функция использует регулярное выражение для идентификации и замены закодированных символов HTML, один символ вовремя.
function unescapeHtml(html) {
var el = document.createElement('div');
return html.replace(/\&[#0-9a-z]+;/gi, function (enc) {
el.innerHTML = enc;
return el.innerText
});
}
Ответ CMS работает нормально, если только HTML-код, который вы хотите удалить, не очень длинный, длиннее 65536 символов. Потому что тогда в Chrome внутренний HTML разбивается на множество дочерних узлов, каждый длиной не более 65536, и вам нужно объединить их. Эта функция работает также для очень длинных строк:
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;
}
Смотрите этот ответ о innerHTML
максимальная длина для получения дополнительной информации: /questions/13204929/ogranichenie-razmera-innerhtml/13204953#13204953
Чтобы отключить экранирование HTML-объектов * в JavaScript, вы можете использовать небольшую библиотеку html-escaper:npm install html-escaper
import {unescape} from 'html-escaper';
unescape('escaped string');
Или unescape
функция из Lodash или Underscore, если вы ее используете.
*) обратите внимание, что эти функции охватывают не все объекты HTML, а только наиболее распространенные, т.е. &
, <
, >
, '
, "
. Для того, чтобы все HTML экранирования в сущности вы можете использовать он библиотеку.
Ответ Криса приятный и элегантный, но он терпит неудачу, если значение не определено. Простое улучшение делает его твердым:
function htmlDecode(value) {
return (typeof value === 'undefined') ? '' : $('<div/>').html(value).text();
}
Добро пожаловать... просто мессенджер... все заслуги передаются на ourcodeworld.com, ссылка ниже.
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);
});
}
};
Полный кредит: https://ourcodeworld.com/articles/read/188/encode-and-decode-html-entities-using-pure-javascript
В вопросе не указано происхождение x
но имеет смысл по возможности защищаться от злонамеренного (или просто неожиданного, со стороны нашего собственного приложения) ввода. Например, предположимx
имеет ценность & <script>alert('hello');</script>
. Безопасный и простой способ справиться с этим в jQuery:
var x = "& <script>alert('hello');</script>";
var safe = $('<div />').html(x).text();
// => "& alert('hello');"
Найдено через https://gist.github.com/jmblog/3222899. Я не вижу многих причин избегать использования этого решения, поскольку оно как минимум такое же короткое, если не короче, чем некоторые альтернативы, и обеспечивает защиту от XSS.
(Изначально я разместил это как комментарий, но добавляю его как ответ, так как последующий комментарий в том же потоке потребовал, чтобы я это сделал).
Не прямой ответ на ваш вопрос, но не лучше ли вашему RPC вернуть некоторую структуру (будь то XML, JSON или что-то еще) с этими данными изображения (URL в вашем примере) внутри этой структуры?
Тогда вы можете просто разобрать его в своем JavaScript и построить <img>
используя сам Javascript.
Структура, которую вы получаете от RPC, может выглядеть так:
{"img" : ["myimage.jpg", "myimage2.jpg"]}
Я думаю, что так будет лучше, поскольку внедрение кода, полученного из внешнего источника, на вашу страницу не выглядит очень безопасным. Представьте, что кто-то захватывает ваш XML-RPC-скрипт и помещает туда что-то, что вам не нужно (даже некоторый javascript...)
Я был достаточно сумасшедшим, чтобы пройти и сделать эту функцию, которая должна быть довольно, если не полностью, исчерпывающей:
function removeEncoding(string) {
return string.replace(/À/g, "À").replace(/Á/g, "Á").replace(/Â/g, "Â").replace(/Ã/g, "Ã").replace(/Ä/g, "Ä").replace(/Å/g, "Å").replace(/à/g, "à").replace(/â/g, "â").replace(/ã/g, "ã").replace(/ä/g, "ä").replace(/å/g, "å").replace(/Æ/g, "Æ").replace(/æ/g, "æ").replace(/ß/g, "ß").replace(/Ç/g, "Ç").replace(/ç/g, "ç").replace(/È/g, "È").replace(/É/g, "É").replace(/Ê/g, "Ê").replace(/Ë/g, "Ë").replace(/è/g, "è").replace(/é/g, "é").replace(/ê/g, "ê").replace(/ë/g, "ë").replace(/ƒ/g, "ƒ").replace(/Ì/g, "Ì").replace(/Í/g, "Í").replace(/Î/g, "Î").replace(/Ï/g, "Ï").replace(/ì/g, "ì").replace(/í/g, "í").replace(/î/g, "î").replace(/ï/g, "ï").replace(/Ñ/g, "Ñ").replace(/ñ/g, "ñ").replace(/Ò/g, "Ò").replace(/Ó/g, "Ó").replace(/Ô/g, "Ô").replace(/Õ/g, "Õ").replace(/Ö/g, "Ö").replace(/ò/g, "ò").replace(/ó/g, "ó").replace(/ô/g, "ô").replace(/õ/g, "õ").replace(/ö/g, "ö").replace(/Ø/g, "Ø").replace(/ø/g, "ø").replace(/Œ/g, "Œ").replace(/œ/g, "œ").replace(/Š/g, "Š").replace(/š/g, "š").replace(/Ù/g, "Ù").replace(/Ú/g, "Ú").replace(/Û/g, "Û").replace(/Ü/g, "Ü").replace(/ù/g, "ù").replace(/ú/g, "ú").replace(/û/g, "û").replace(/ü/g, "ü").replace(/µ/g, "µ").replace(/×/g, "×").replace(/Ý/g, "Ý").replace(/Ÿ/g, "Ÿ").replace(/ý/g, "ý").replace(/ÿ/g, "ÿ").replace(/°/g, "°").replace(/†/g, "†").replace(/‡/g, "‡").replace(/</g, "<").replace(/>/g, ">").replace(/±/g, "±").replace(/«/g, "«").replace(/»/g, "»").replace(/¿/g, "¿").replace(/¡/g, "¡").replace(/·/g, "·").replace(/•/g, "•").replace(/™/g, "™").replace(/©/g, "©").replace(/®/g, "®").replace(/§/g, "§").replace(/¶/g, "¶").replace(/Α/g, "Α").replace(/Β/g, "Β").replace(/Γ/g, "Γ").replace(/Δ/g, "Δ").replace(/Ε/g, "Ε").replace(/Ζ/g, "Ζ").replace(/Η/g, "Η").replace(/Θ/g, "Θ").replace(/Ι/g, "Ι").replace(/Κ/g, "Κ").replace(/Λ/g, "Λ").replace(/Μ/g, "Μ").replace(/Ν/g, "Ν").replace(/Ξ/g, "Ξ").replace(/Ο/g, "Ο").replace(/Π/g, "Π").replace(/Ρ/g, "Ρ").replace(/Σ/g, "Σ").replace(/Τ/g, "Τ").replace(/Υ/g, "Υ").replace(/Φ/g, "Φ").replace(/Χ/g, "Χ").replace(/Ψ/g, "Ψ").replace(/Ω/g, "Ω").replace(/α/g, "α").replace(/β/g, "β").replace(/γ/g, "γ").replace(/δ/g, "δ").replace(/ε/g, "ε").replace(/ζ/g, "ζ").replace(/η/g, "η").replace(/θ/g, "θ").replace(/ι/g, "ι").replace(/κ/g, "κ").replace(/λ/g, "λ").replace(/μ/g, "μ").replace(/ν/g, "ν").replace(/ξ/g, "ξ").replace(/ο/g, "ο").replace(/&piρ;/g, "ρ").replace(/ρ/g, "ς").replace(/ς/g, "ς").replace(/σ/g, "σ").replace(/τ/g, "τ").replace(/φ/g, "φ").replace(/χ/g, "χ").replace(/ψ/g, "ψ").replace(/ω/g, "ω").replace(/•/g, "•").replace(/…/g, "…").replace(/′/g, "′").replace(/″/g, "″").replace(/‾/g, "‾").replace(/⁄/g, "⁄").replace(/℘/g, "℘").replace(/ℑ/g, "ℑ").replace(/ℜ/g, "ℜ").replace(/™/g, "™").replace(/ℵ/g, "ℵ").replace(/←/g, "←").replace(/↑/g, "↑").replace(/→/g, "→").replace(/↓/g, "↓").replace(/&barr;/g, "↔").replace(/↵/g, "↵").replace(/⇐/g, "⇐").replace(/⇑/g, "⇑").replace(/⇒/g, "⇒").replace(/⇓/g, "⇓").replace(/⇔/g, "⇔").replace(/∀/g, "∀").replace(/∂/g, "∂").replace(/∃/g, "∃").replace(/∅/g, "∅").replace(/∇/g, "∇").replace(/∈/g, "∈").replace(/∉/g, "∉").replace(/∋/g, "∋").replace(/∏/g, "∏").replace(/∑/g, "∑").replace(/−/g, "−").replace(/∗/g, "∗").replace(/√/g, "√").replace(/∝/g, "∝").replace(/∞/g, "∞").replace(/&OEig;/g, "Œ").replace(/œ/g, "œ").replace(/Ÿ/g, "Ÿ").replace(/♠/g, "♠").replace(/♣/g, "♣").replace(/♥/g, "♥").replace(/♦/g, "♦").replace(/ϑ/g, "ϑ").replace(/ϒ/g, "ϒ").replace(/ϖ/g, "ϖ").replace(/Š/g, "Š").replace(/š/g, "š").replace(/∠/g, "∠").replace(/∧/g, "∧").replace(/∨/g, "∨").replace(/∩/g, "∩").replace(/∪/g, "∪").replace(/∫/g, "∫").replace(/∴/g, "∴").replace(/∼/g, "∼").replace(/≅/g, "≅").replace(/≈/g, "≈").replace(/≠/g, "≠").replace(/≡/g, "≡").replace(/≤/g, "≤").replace(/≥/g, "≥").replace(/⊂/g, "⊂").replace(/⊃/g, "⊃").replace(/⊄/g, "⊄").replace(/⊆/g, "⊆").replace(/⊇/g, "⊇").replace(/⊕/g, "⊕").replace(/⊗/g, "⊗").replace(/⊥/g, "⊥").replace(/⋅/g, "⋅").replace(/&lcell;/g, "⌈").replace(/&rcell;/g, "⌉").replace(/⌊/g, "⌊").replace(/⌋/g, "⌋").replace(/⟨/g, "⟨").replace(/⟩/g, "⟩").replace(/◊/g, "◊").replace(/'/g, "'").replace(/&/g, "&").replace(/"/g, "\"");
}
Используется так:
let decodedText = removeEncoding("Ich heiße David");
console.log(decodedText);
Печать: Ich Heiße David
PS это заняло около полутора часов.
Это наиболее полное решение, которое я пробовал до сих пор:
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]
);
};
Я знаю, что здесь есть много хороших ответов, но, поскольку я реализовал немного другой подход, я решил поделиться.
Этот код представляет собой совершенно безопасный подход с точки зрения безопасности, поскольку обработчик экранирования зависит от браузера, а не от функции. Таким образом, если в будущем будет обнаружена новая уязвимость, это решение будет покрыто.
const decodeHTMLEntities = text => {
// Create a new element or use one from cache, to save some element creation overhead
const el = decodeHTMLEntities.__cache_data_element
= decodeHTMLEntities.__cache_data_element
|| document.createElement('div');
const enc = text
// Prevent any mixup of existing pattern in text
.replace(/⪪/g, '⪪#')
// Encode entities in special format. This will prevent native element encoder to replace any amp characters
.replace(/&([a-z1-8]{2,31}|#x[0-9a-f]+|#\d+);/gi, '⪪$1⪫');
// Encode any HTML tags in the text to prevent script injection
el.textContent = enc;
// Decode entities from special format, back to their original HTML entities format
el.innerHTML = el.innerHTML
.replace(/⪪([a-z1-8]{2,31}|#x[0-9a-f]+|#\d+)⪫/gi, '&$1;')
.replace(/#⪫/g, '⪫');
// Get the decoded HTML entities
const dec = el.textContent;
// Clear the element content, in order to preserve a bit of memory (it is just the text may be pretty big)
el.textContent = '';
return dec;
}
// Example
console.log(decodeHTMLEntities("<script>alert('∳∳∳∳⪪#x02233⪫');</script>"));
// Prints: <script>alert('∳∳∳∳⪪##x02233⪫');</script>
Кстати, я решил использовать символы
⪪
а также
⪫
, потому что они используются редко, поэтому вероятность влияния на производительность путем их сопоставления значительно ниже.
Это лучше:
String::decode = ->
$('<textarea />').html(this).text()
использовать:
"<img src='myimage.jpg'>".decode();
Замыкания позволяют избежать создания ненужных объектов.
const decodingHandler = (() => {
const element = document.createElement('div');
return text => {
element.innerHTML = text;
return element.textContent;
};
})();
Более лаконичный способ
const decodingHandler = (() => {
const element = document.createElement('div');
return text => ((element.innerHTML = text), element.textContent);
})();
Используйте идентичность! Ни один из приведенных выше ответов меня не удовлетворил, поэтому я выбрал некоторые вещи отсюда, исправил их проблемы и добавил полные определения сущностей W3C, а также некоторые дополнительные функции. Я также сделал его как можно меньшим: теперь он составляет 31 КБ в минифицированном виде и 14 КБ в сжатом виде. Вы можете скачать его с https://github.com/arashkazemi/dentity .
Он включает в себя функции декодера и кодировщика и работает как в браузере, так и в среде узла. Надеюсь, это эффективно решит проблему!
Я использую это в своем проекте: вдохновленный другими ответами, но с дополнительным безопасным параметром, может быть полезен, когда вы имеете дело с украшенными персонажами
var decodeEntities=(function(){
var el=document.createElement('div');
return function(str, safeEscape){
if(str && typeof str === 'string'){
str=str.replace(/\</g, '<');
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, '<');
}
return str;
}
})();
И это можно использовать как:
var label='safe <b> character éntity</b>';
var safehtml='<div title="'+decodeEntities(label)+'">'+decodeEntities(label, true)+'</div>';
Все остальные ответы здесь имеют проблемы.
Методы document.createElement('div') (включая методы, использующие jQuery) выполняют любой переданный в него javascript (проблема безопасности), а метод DOMParser.parseFromString() удаляет пробелы. Вот чистое решение JavaScript, которое не имеет ни одной проблемы:
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 используется специально, чтобы избежать выполнения кода JS. Это проходит эти:
htmlDecode('<& >'); // 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.
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);
// decode-html.js v1
function decodeHtml(html) {
const textarea = document.createElement('textarea');
textarea.innerHTML = html;
const decodedHtml = textarea.textContent;
textarea.remove();
return decodedHtml;
};
// encode-html.js v1
function encodeHtml(html) {
const textarea = document.createElement('textarea');
textarea.textContent = html;
const encodedHtml = textarea.innerHTML;
textarea.remove();
return encodedHtml;
};
// example of use:
let htmlDecoded = 'one & two & three';
let htmlEncoded = 'one & two & three';
console.log(1, htmlDecoded);
console.log(2, encodeHtml(htmlDecoded));
console.log(3, htmlEncoded);
console.log(4, decodeHtml(htmlEncoded));
function decodeHTMLContent(htmlText) {
var txt = document.createElement("span");
txt.innerHTML = htmlText;
return txt.innerText;
}
var result = decodeHTMLContent('One & two & three');
console.log(result);
Есть вариант, который на 80% продуктивнее ответов на самом верху.
См. Тест: https://jsperf.com/decode-html12345678/1
console.log(decodeEntities('test: >'));
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;
}
Если вам нужно оставить метки, то удалите два .replace(...)
звонки (вы можете оставить первый, если вам не нужны скрипты).
Текущий ответ с наибольшим количеством голосов имеет тот недостаток, что он удаляет HTML из строки. Если это не то, что вы хотите (это, конечно, не было частью вопроса), я предлагаю использовать регулярное выражение для поиска объектов HTML (/&[^;]*;/gmi
), а затем перебирать совпадения и просто преобразовывать их.
Что касается XSS-атак:
ПокаinnerHTML
не выполняет код в<script>
теги, код может выполняться вon*
атрибуты события, поэтому одно только приведенное выше регулярное выражение может быть использовано пользователем, передающим строку, например:
&<img src='asdfa' error='alert(`doin\' me a hack`)' />;
По этой причине необходимо преобразовать любой<
персонажей к их<
прежде чем помещать их в скрытый элемент div.
Кроме того, просто чтобы охватить все мои основы, я отмечу, что функции, определенные таким образом, в глобальной области видимости могут быть перезаписаны путем переопределения их в консоли, поэтому важно либо определить эту функцию с помощьюconst
или поместите его в неглобальную область.
Примечание. Попытки эксплойта в следующем примере сбивают с толку редактор фрагментов стека из-за выполняемой им предварительной обработки, поэтому вам придется запускать его в консоли браузера или в его собственном файле, чтобы увидеть результат.
var tests = [
"here's a spade: ♠!",
"&<script>alert('hackerman')</script>;",
"&<img src='asdfa' error='alert(`doin\' me a hack`)' />;",
"<b>↑ CAN'T HACK ME, BRO</b>"
];
var decoded = tests.map(decodeHTMLEntities).join("\n");
console.log(decoded);
Результат:
here's a spade: ♠!
&<script>alert('hackerman')</script>;
&<img src='asdfa' error='alert(`doin' me a hack`)' />;
<b>↑ CAN'T HACK ME, BRO</b>