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/

Каковы другие способы диагностики проблемы?

Ответ 1

ОБНОВЛЕНИЕ: Вы должны использовать API DOMParser, поскольку Владимир предлагает, я отредактировал свой предыдущий ответ, поскольку опубликованная функция представила уязвимость безопасности.

Следующий фрагмент кода представляет собой старый код ответа с небольшой модификацией: использование textarea вместо div снижает уязвимость XSS, но все еще проблематично в IE9 и Firefox.

function htmlDecode(input){
  var e = document.createElement('textarea');
  e.innerHTML = input;
  // handle case of empty input
  return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}

htmlDecode("&lt;img src='myimage.jpg'&gt;"); 
// returns "<img src='myimage.jpg'>"

По сути, я создаю элемент DOM программно, назначаю закодированный HTML-код его innerHTML и извлекаю nodeValue из текстового узла, созданного при вставке innerHTML. Поскольку он просто создает элемент, но никогда не добавляет его, HTML-код сайта не изменяется.

Он будет работать в кросс-браузерном режиме (включая старые браузеры) и принимать все символьные объекты HTML.

ОБНОВЛЕНИЕ: старая версия этого кода не работала в IE с пустыми вводами, о чем свидетельствует здесь на jsFiddle (просмотр в IE). Вышеприведенная версия работает со всеми входами.

ОБНОВЛЕНИЕ: кажется, это не работает с большой строкой, и это также вводит уязвимость безопасности, см. комментарии.

Ответ 2

Большинство приведенных здесь ответов имеют огромный недостаток: если строка, которую вы пытаетесь преобразовать, не доверяет, вы получите Межсайтовый скриптинг (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("&lt;img src='myimage.jpg'&gt;");

// 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 (обычно их все еще недостаточно, чтобы беспокоиться).

Ответ 3

Если вы используете jQuery:

function htmlDecode(value){ 
  return $('<div/>').html(value).text(); 
}

В противном случае используйте Strictly Software Encoder Object, который имеет отличную функцию htmlDecode().

Ответ 4

Хитрость заключается в использовании мощности браузера для декодирования специальных символов 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
    });
}

Ответ 5

Ответ 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 max length для получения дополнительной информации: fooobar.com/questions/65916/...

Ответ 6

Не прямой ответ на ваш вопрос, но не лучше ли для вашего RPC вернуть некоторую структуру (будь то XML или JSON или что-то еще) с этими данными изображения (URL-адреса в вашем примере) внутри этой структуры?

Затем вы можете просто проанализировать его в своем javascript и построить <img> с помощью самого javascript.

Структура, которую вы получите из RPC, может выглядеть так:

{"img" : ["myimage.jpg", "myimage2.jpg"]}

Я думаю, что так лучше, поскольку ввод кода, который поступает из внешнего источника на вашу страницу, выглядит не очень безопасным. Изображая кого-то, угоняющего ваш XML-RPC script, и помещайте что-то, чего вы не хотели бы там (даже некоторый javascript...)

Ответ 7

Ответ Криса приятный и элегантный, но он терпит неудачу, если значение undefined. Простое улучшение делает его прочным:

function htmlDecode(value) {
   return (typeof value === 'undefined') ? '' : $('<div/>').html(value).text();
}

Ответ 8

Не за что... просто посыльный... полный кредит переходит на 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

Ответ 10

Я использую это в своем проекте: вдохновленный другими ответами, но с дополнительным безопасным параметром, может быть полезен, когда вы имеете дело с украшенными символами

var decodeEntities=(function(){

    var el=document.createElement('div');
    return function(str, safeEscape){

        if(str && typeof str === 'string'){

            str=str.replace(/\</g, '&lt;');

            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, '&lt;');
        }
        return str;
    }
})();

И это применимо как:

var label='safe <b> character &eacute;ntity</b>';
var safehtml='<div title="'+decodeEntities(label)+'">'+decodeEntities(label, true)+'</div>';

Ответ 11

Все остальные ответы здесь имеют проблемы.

Методы 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 используется специально для того, чтобы избежать выполнения кода jQuery. Он передает следующее:

htmlDecode('&lt;&amp;&nbsp;&gt;'); // 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.

Ответ 12

Вместо экранирования строки, запретите пользователю вводить одну строку

function EscapeInputValue(inputValue) {
    return /^(?:[^\<\>\<\\\>]*)(?:[^\<\>\<\\\>]*)$/.test(inputValue);
}

Ура! :)

Ответ 13

Есть вариант, который на 80% продуктивнее ответов на самом верху.

См. Тест: https://jsperf.com/decode-html12345678/1

performance test

console.log(decodeEntities('test: &gt'));

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;
}