在 JavaScript 中取消转义 HTML 实体?

IT技术 javascript html escaping xml-rpc
2021-01-18 23:10:54

我有一些与 XML-RPC 后端通信的 JavaScript 代码。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/

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

6个回答

这里给出的大多数答案都有一个巨大的缺点:如果您尝试转换的字符串不受信任,那么您最终会遇到跨站点脚本 (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;
}

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

console.log(  htmlDecode("<img src='dummy' onerror='alert(/xss/)'>")  )  
// ""

此函数保证不会运行任何 JavaScript 代码作为副作用。任何 HTML 标签都将被忽略,只会返回文本内容。

兼容性说明:解析 HTMLDOMParser至少需要 Chrome 30、Firefox 12、Opera 17、Internet Explorer 10、Safari 7.1 或 Microsoft Edge。因此,所有不支持的浏览器都已经过了 EOL,截至 2017 年,唯一仍然可以在野外偶尔看到的浏览器是较旧的 Internet Explorer 和 Safari 版本(通常这些仍然不足以打扰)。

@PointedEars:<script>未执行的标签不是一种安全机制,如果设置innerHTML可以运行同步脚本作为副作用,此规则只是避免了棘手的计时问题清理 HTML 代码是一件棘手的事情,innerHTML甚至不会尝试 - 已经因为网页可能实际上打算设置内联事件处理程序。这根本不是一种用于不安全数据的机制,句号。
2021-03-19 23:10:54
@PointedEars:谁在乎 2016 年的 Firefox 12?有问题的是 Internet Explorer 最高 9.0 和 Safari 最高 7.0。如果人们负担不起不支持它们(希望很快就会成为每个人),那么 DOMParser 是最佳选择。如果不是 - 是的,仅处理实体将是一种选择。
2021-03-30 23:10:54
我认为这个答案是最好的,因为它提到了 XSS 漏洞。
2021-04-01 23:10:54
@ИльяЗеленько:您是否打算在紧密循环中使用此代码,或者为什么性能很重要?你的答案再次容易受到 XSS 攻击,真的值得吗?
2021-04-03 23:10:54
请注意(根据您的参考)在 Firefox 12.0 之前DOMParser不支持"text/html",并且仍有一些最新版本的浏览器甚至不支持DOMParser.prototype.parseFromString(). 根据您的参考,DOMParser仍然是一项实验性技术,替代品使用的innerHTML属性,正如您在回应我的方法时所指出的那样,具有此 XSS 漏洞(应该由浏览器供应商修复)。
2021-04-06 23:10:54

你需要解码所有编码的 HTML 实体还是只解码&amp;它本身?

如果你只需要处理,&amp;那么你可以这样做:

var decoded = encoded.replace(/&amp;/g, '&');

如果您需要解码所有 HTML 实体,那么您可以在没有 jQuery 的情况下完成:

var elem = document.createElement('textarea');
elem.innerHTML = encoded;
var decoded = elem.value;

请注意下面 Mark 的评论,这些评论强调了此答案早期版本中的安全漏洞,并建议使用textarea而不是div缓解潜在的 XSS 漏洞。无论您使用 jQuery 还是普通的 JavaScript,这些漏洞都存在。

谨防!这可能是不安全的。如果encoded='<img src="bla" onerror="alert(1)">'那么上面的代码段将显示警报。这意味着如果您的编码文本来自用户输入,则使用此代码段对其进行解码可能会存在 XSS 漏洞。
2021-03-09 23:10:54
@MarkAmery 我不是安全专家,但看起来如果您null在获取文本后立即将 div 设置为,则不会触发 img 中的警报 - jsfiddle.net/Mottie/gaBeb/128
2021-03-10 23:10:54
如何在 Node 服务器上执行此操作?
2021-03-14 23:10:54
@Mottie 请注意确定哪种浏览器适合您,但alert(1)在 OS X 上的 Chrome 上仍然为我触发。如果您想要此 hack 的安全变体,请尝试使用textarea.
2021-03-28 23:10:54
+1 用于简单的正则表达式替换仅一种 html 实体的替代方案。如果您希望将 html 数据从 Python Flask 应用程序插入到模板,请使用它。
2021-04-02 23:10:54

编辑:您应该按照Wladimir 的建议使用 DOMParser API ,由于发布的函数引入了安全漏洞,因此我编辑了我之前的答案。

以下代码片段是旧答案的代码,稍作修改:使用 atextarea而不是 adiv减少了 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 并从在 innerHTML 插入时创建的文本节点中检索 nodeValue。由于它只是创建一个元素但从不添加它,因此不会修改站点 HTML。

它将跨浏览器(包括旧浏览器)工作并接受所有HTML 字符实体

编辑:此代码的旧版本在具有空白输入的 IE上不起作用,正如在 jsFiddle 上所证明的(在 IE 中查看)。上面的版本适用于所有输入。

更新:似乎这不适用于大字符串,并且还引入了安全漏洞,请参阅评论。

另请参阅@kender 关于此方法安全性差的说明。
2021-03-11 23:10:54
这个函数存在安全隐患,即使元素没有被添加到 DOM 中,JavaScript 代码也会运行。所以这只有在输入字符串可信时才可以使用。我添加了我自己的答案来解释这个问题并提供一个安全的解决方案。作为副作用,如果存在多个文本节点,结果不会被截断。
2021-03-12 23:10:54
@S.Mark:&apos;不属于 HTML 4 实体,这就是原因!w3.org/TR/html4/sgml/entities.html fishbowl.pastiche.org/2003/07/01/the_curse_of_apos
2021-03-20 23:10:54
请参阅我给@kender 的关于他所做的糟糕测试的说明;)
2021-04-07 23:10:54
如果 JS 未在浏览器中运行,即使用 Node,这将不起作用。
2021-04-07 23:10:54

从 JavaScript 解释 HTML(文本和其他)的一个更现代的选项是DOMParserAPI 中的 HTML 支持参见 MDN 中的此处)。这允许您使用浏览器的本机 HTML 解析器将字符串转换为 HTML 文档。自 2014 年底以来,所有主要浏览器的新版本都支持它。

如果我们只是想解码一些文本内容,我们可以将其作为文档正文中的唯一内容,解析文档,然后将其提取出来.body.textContent

var encodedStr = 'hello &amp; world';

var parser = new DOMParser;
var dom = parser.parseFromString(
    '<!doctype html><body>' + encodedStr,
    'text/html');
var decodedString = dom.body.textContent;

console.log(decodedString);

我们可以在规范草案中DOMParser看到,没有为解析的文档启用 JavaScript,因此我们可以在没有安全问题的情况下执行此文本转换。

parseFromString(str, type)方法必须运行这些步骤,具体取决于类型

  • "text/html"

    解析海峡HTML parser,并返回新创建的Document

    脚本标志必须设置为“禁用”。

    笔记

    script元素被标记为不可执行,内容noscript被解析为标记。

这超出了这个问题的范围,但请注意,如果您将解析的 DOM 节点本身(不仅仅是它们的文本内容)移动到实时文档 DOM,则它们的脚本可能会重新启用,并且可能是安全问题。我没有研究过,所以请谨慎行事。

2021-03-10 23:10:54
NodeJs的任何替代品?
2021-03-18 23:10:54

Matthias Bynens 有一个库:https : //github.com/mathiasbynens/he

例子:

console.log(
    he.decode("J&#246;rg &amp J&#xFC;rgen rocked to &amp; fro ")
);
// Logs "Jörg & Jürgen rocked to & fro"

我建议支持它而不是涉及设置元素的 HTML 内容然后读回其文本内容的 hack。这种方法可以工作,但如果用于不受信任的用户输入,则具有欺骗性的危险并存在 XSS 机会。

如果你真的不忍心加载库,你可以使用这个答案中textarea描述hack 来解决一个几乎重复的问题,与建议的各种类似方法不同,它没有我所知道的安全漏洞:

function decodeEntities(encodedString) {
    var textArea = document.createElement('textarea');
    textArea.innerHTML = encodedString;
    return textArea.value;
}

console.log(decodeEntities('1 &amp; 2')); // '1 & 2'

但请注意安全问题,影响与此方法类似的方法,我在链接的答案中列出了这些问题!这种方法是一种黑客行为,未来对 a 的允许内容textarea(或特定浏览器中的错误)的更改可能会导致依赖它的代码有一天突然出现 XSS 漏洞。

Matthias Bynens 的图书馆he非常棒!非常感谢您的推荐!
2021-03-20 23:10:54