在JavaScript中如何取消转义HTML实体?

293

我有一些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/

还有哪些方法可以诊断问题?


这篇文章中包含的庞大函数似乎运行良好:http://blogs.msdn.com/b/aoakley/archive/2003/11/12/49645.aspx 我认为这不是最聪明的解决方案,但它能够工作。 - mati
2
作为包含HTML实体的字符串与escapeURI编码字符串不同,因此这些函数无法使用。 - Marcel Korpel
2
@Matias 注意,自2003年编写该函数以来,HTML(例如通过HTML 5规范)已添加了新的命名实体-例如,它无法识别“𝕫”。这是一个不断发展的规范问题;因此,您应选择一个实际正在维护的工具来解决它。 - Mark Amery
可能是如何使用jQuery解码HTML实体?的重复问题。 - lucascaro
我刚刚意识到很容易将这个问题与编码HTML实体混淆。我刚刚意识到我在这个问题上不小心发布了一个错误的答案!不过我已经删除了它。 - shreyasm-dev
34个回答

14

如果你像我一样在寻找它,那么现在有一个不错且安全的JQuery方法。

https://api.jquery.com/jquery.parsehtml/

你可以在控制台中键入以下内容:

var x = "test &amp;";
> undefined
$.parseHTML(x)[0].textContent
> "test &"

因此,$.parseHTML(x)返回一个数组,如果您的文本中有HTML标记,则数组长度将大于1。


1
如果 x 的值为 <script>alert('hello');</script>,上面的代码会崩溃。在当前的 jQuery 中,它实际上不会尝试运行脚本,但是 [0] 将产生 undefined,因此对 textContent 的调用将失败,并且您的脚本将在那里停止。 $('<div />').html(x).text(); 看起来更安全 - 来自 https://gist.github.com/jmblog/3222899 - Andrew Hodgkinson
@AndrewHodgkinson 是的,但问题是“在JavaScript中解码并返回&”-因此您首先要测试x的内容或确保仅在正确的情况下使用它。 - cslotty
我真的不明白这是怎么回事。上面的代码在所有情况下都能正常工作。你到底要如何“确保”x的值需要修复呢?如果上面的脚本示例警告了“&”,以至于它确实需要更正,那该怎么办呢?我们不知道OP的字符串来自哪里,因此必须考虑恶意输入的情况。 - Andrew Hodgkinson
@AndrewHodgkinson,我喜欢你的考虑,但这不是问题所在。不过,如果你愿意,可以回答那个问题。我猜你可以删除脚本标签,例如。 - cslotty
@SergioA。谢谢,完成了:https://dev59.com/o3I-5IYBdhLWcg3wVWpi#60645505 - Andrew Hodgkinson
显示剩余2条评论

10

jQuery会为您进行编码和解码,但您需要使用textarea标签,而不是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,因为旧版本的jQuery存在(令人惊讶的)安全漏洞,其中一些版本可能仍然拥有大量用户-这些版本将在传递给.html()的HTML中检测并显式评估脚本。因此,即使使用textarea也不足以确保安全性;我建议不要使用jQuery来完成此任务,并使用纯DOM API编写等效代码。(是的,那种旧行为是疯狂和可怕的。) - Mark Amery
谢谢您指出这一点。然而,该问题并未包括检查脚本注入的要求。该问题明确询问了由Web服务器呈现的HTML内容。在保存之前,应该对保存到Web服务器的HTML内容进行脚本注入验证。 - Jason Williams
我使用了你的示例并制作了基本版本(在页面下方)。 - Luis Lobo

6

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最大长度的更多信息,请参见此答案:https://dev59.com/aErSa4cB1Zd3GeqPavvb#27545633


5

要在JavaScript中取消转义HTML实体,您可以使用小型库html-escapernpm install html-escaper

*实体:指HTML中的特殊字符,例如:<表示小于号 (<)。
import {unescape} from 'html-escaper';

unescape('escaped string');

或者,如果你在使用它们,可以使用来自LodashUnderscoreunescape函数。

*) 请注意,这些功能并不涵盖所有的HTML实体,只涵盖最常见的实体,即&<>'"。如果要取消转义所有HTML实体,您可以使用he库。


4

首先在页面中创建一个<span id="decodeIt" style="display:none;"></span>

接着,将待解码的字符串赋值给此标签的innerHTML属性:

document.getElementById("decodeIt").innerHTML=stringtodecode

最后,
stringtodecode=document.getElementById("decodeIt").innerText

以下是整个代码:

var stringtodecode="<B>Hello</B> world<br>";
document.getElementById("decodeIt").innerHTML=stringtodecode;
stringtodecode=document.getElementById("decodeIt").innerText

2
-1;这在不受信任的输入上使用是非常不安全的。例如,考虑一下如果 stringtodecode 包含类似于 <script>alert(1)</script> 的内容会发生什么。 - Mark Amery

4

这个问题没有明确说明x的来源,但如果可能的话,我们应该防御恶意的(或来自我们自己应用程序的)意外输入。例如,假设x的值为&amp; <script>alert('hello');</script>。在jQuery中处理这种情况的一种安全而简单的方式是:

var x    = "&amp; <script>alert('hello');</script>";
var safe = $('<div />').html(x).text();

// => "& alert('hello');"

通过 https://gist.github.com/jmblog/3222899 发现此方法。鉴于该方法至少与某些替代方案一样短,并且可以提供防御XSS攻击的保护,我认为没有太多理由避免使用这个解决方案。

(我最初将其发布为评论,但由于同一线程中的后续评论要求我这样做,所以我将其添加为答案)。


2

对于新手来说:

const htmlDecode = innerHTML => Object.assign(document.createElement('textarea'), {innerHTML}).value;

console.log(htmlDecode('Complicated - Dimitri Vegas &amp; Like Mike'));

2

虽然不是直接回答你的问题,但是如果你的RPC返回一些结构(无论是XML还是JSON等),并在该结构中包含这些图像数据(例如URL),那不是更好吗?

然后你只需在JavaScript中解析它,并使用JavaScript本身构建<img>

从RPC收到的结构可能如下所示:

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

我认为这样做更好,因为将来自外部源的代码注入到您的页面中看起来并不太安全。想象一下有人劫持了您的XML-RPC脚本,并在其中放置了一些您不希望出现的内容(甚至是一些JavaScript...)


以上的 @CMS 方法是否存在这种安全漏洞? - Joseph Turian
我刚刚检查了传递给htmlDecode函数的以下参数:htmlDecode("<img src='myimage.jpg'><script>document.write('xxxxx');</script>"),它会创建可能不好的<script></script>元素,以我的看法。而且我仍然认为返回一个结构而不是要插入的文本更好,例如您可以很好地处理错误。 - kender
1
我刚刚尝试了 htmlDecode("&lt;img src='myimage.jpg'&gt;&lt;script&gt;alert('xxxxx');&lt;/script&gt;"),但什么也没发生。我按预期收到了解码后的 HTML 字符串。 - Roatin Marth

2
我知道这里有很多好的答案,但是由于我实施了一个略微不同的方法,所以想分享一下。
从安全角度考虑,这段代码是完全安全的,因为转义处理程序依赖于浏览器,而不是函数。因此,如果将来发现新的漏洞,这个解决方案也会得到覆盖。
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('&awconint;&CounterClockwiseContourIntegral;&#x02233;&#8755;⪪#x02233⪫');</script>"));
// Prints: <script>alert('∳∳∳∳⪪##x02233⪫');</script>

顺便说一下,我选择使用字符,因为它们很少被使用,所以匹配它们对性能的影响显著降低。


2
你好,这是最初的回答。感谢您使用我们的服务。只是一个传递者,所有的功劳归于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

此文介绍了如何使用纯JavaScript编码和解码HTML实体。

1
这是一个不完整的解决方案;它只处理十进制数字字符引用,不处理命名字符引用或十六进制数字字符引用。 - Mark Amery

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接