解码和 返回和加入JavaScript


229

我有像

var str = 'One & two & three';

由Web服务器呈现为HTML。我需要将这些字符串转换为

'One & two & three'

当前,这就是我正在做的事情(在jQuery的帮助下):

$(document.createElement('div')).html('{{ driver.person.name }}').text()

但是我有一种不安的感觉,我做错了。我努力了

unescape("&")

但它似乎无效,decodeURI / decodeURIComponent也无效。

还有其他更自然,更优雅的方式吗?


本文中包含的强大功能似乎运行良好:blogs.msdn.com/b/aoakley/archive/2003/11/12/49645.aspx我认为这不是最聪明的解决方案,但可以运行。
Matias

1
由于包含HTML实体的字符串与escaped或URI编码的字符串不同,因此这些功能将不起作用。
马塞尔·科佩尔

1
@Matias指出,自该函数于2003年创作以来,已将新的命名实体添加到HTML(例如,通过HTML 5规范)-例如,它无法识别𝕫。这是规格不断演变的问题。因此,您应该选择一个实际维护的工具来解决它。
Mark Amery

1
@MarkAmery是的,我完全同意!几年后再次回到这个问题,真是太好了,谢谢!
Matias

Answers:


104

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

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

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

我们可以在规范草案中DOMParser看到,已解析的文档未启用JavaScript,因此我们可以执行此文本转换而无需担心安全性。

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

  • "text/html"

    用an 解析strHTML parser,并返回新创建的Document

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

    注意

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

这超出了此问题的范围,但是请注意,如果您将已解析的DOM节点本身(而不仅仅是它们的文本内容)移到实时文档DOM中,则可能会重新启用其脚本,并且可以出于安全考虑。我尚未对此进行研究,因此请谨慎行事。


5
NodeJ的其他选择吗?
coderInrRain

284

您需要解码所有编码的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,这些漏洞都存在。


16
谨防!这可能是不安全的。如果是encoded='<img src="bla" onerror="alert(1)">'这样,上面的代码段将显示警报。这意味着,如果您的编码文本来自用户输入,则使用此代码片段对其进行解码可能会出现XSS漏洞。
Mark Amery

@MarkAmery我不是安全专家,但是看起来如果您null在获取文本后立即将div设置为,则不会触发img中的警报-jsfiddle.net/Mottie/gaBeb/128
Mottie

4
@Mottie请注意,哪种浏览器最适合您,但alert(1)仍会在OS X的Chrome上为我启用。如果您想要此hack的安全变体,请尝试使用textarea
Mark Amery 2015年

+1用于简单的regexp替换替代品,仅用于一种html实体。如果您希望将html数据从python flask应用程序插值到模板,请使用此方法。
OzzyTheGiant

如何在节点服务器上执行此操作?
Mohammad Kermani

44

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内容然后读回其文本内容的黑客攻击时偏爱它。这样的方法可以工作,但是具有欺骗性,如果用于不受信任的用户输入,则存在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'

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


Matthias Bynens的图书馆he绝对很棒!非常感谢您的推荐!
Pedro A

23
var htmlEnDeCode = (function() {
    var charToEntityRegex,
        entityToCharRegex,
        charToEntity,
        entityToChar;

    function resetCharacterEntities() {
        charToEntity = {};
        entityToChar = {};
        // add the default set
        addCharacterEntities({
            '&amp;'     :   '&',
            '&gt;'      :   '>',
            '&lt;'      :   '<',
            '&quot;'    :   '"',
            '&#39;'     :   "'"
        });
    }

    function addCharacterEntities(newEntities) {
        var charKeys = [],
            entityKeys = [],
            key, echar;
        for (key in newEntities) {
            echar = newEntities[key];
            entityToChar[key] = echar;
            charToEntity[echar] = key;
            charKeys.push(echar);
            entityKeys.push(key);
        }
        charToEntityRegex = new RegExp('(' + charKeys.join('|') + ')', 'g');
        entityToCharRegex = new RegExp('(' + entityKeys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
    }

    function htmlEncode(value){
        var htmlEncodeReplaceFn = function(match, capture) {
            return charToEntity[capture];
        };

        return (!value) ? value : String(value).replace(charToEntityRegex, htmlEncodeReplaceFn);
    }

    function htmlDecode(value) {
        var htmlDecodeReplaceFn = function(match, capture) {
            return (capture in entityToChar) ? entityToChar[capture] : String.fromCharCode(parseInt(capture.substr(2), 10));
        };

        return (!value) ? value : String(value).replace(entityToCharRegex, htmlDecodeReplaceFn);
    }

    resetCharacterEntities();

    return {
        htmlEncode: htmlEncode,
        htmlDecode: htmlDecode
    };
})();

这是来自ExtJS源代码。


4
-1; 这无法处理绝大多数命名实体。例如,htmlEnDecode.htmlDecode('&euro;')应该返回'€',但是返回'&euro;'
Mark Amery


15

您可以使用Lodash unescape /转义功能https://lodash.com/docs/4.17.5#unescape

import unescape from 'lodash/unescape';

const str = unescape('fred, barney, &amp; pebbles');

str将成为 'fred, barney, & pebbles'


1
可能最好做“从'lodash / unescape'导入_unescape;” 因此它与同名的已弃用javascript函数没有冲突:unescape
Rick Penabella

14

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

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

你可以f.ex. 在控制台中输入:

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

因此$ .parseHTML(x)返回一个数组,并且如果文本中包含HTML标记,则array.length将大于1。


非常适合我,这正是我想要的,谢谢。
乔纳森·尼尔森

1
如果x具有上述值,<script>alert('hello');</script>将崩溃。在当前的jQuery中,它实际上不会尝试运行脚本,但是[0]会产生结果,undefined因此对的调用textContent将失败,并且脚本将在那里停止。$('<div />').html(x).text();看起来更安全-通过gist.github.com/jmblog/3222899
安德鲁·霍奇金森

@AndrewHodgkinson是的,但问题是“将JavaScript解码并返回到&in JavaScript”-因此,您应首先测试x的内容,或确保仅在正确的情况下使用它。
cslotty

我真的不知道怎么回事。上面的代码在所有情况下均适用。您究竟将如何“确保” x的值固定?如果上面的脚本示例警告了“&amp;”,该怎么办?真的需要更正吗?我们不知道OP的字符串来自何处,因此必须考虑恶意输入。
Andrew Hodgkinson

@AndrewHodgkinson我喜欢您的考虑,但这不是这里的问题。不过,请随时回答该问题。我猜你可以删除脚本标签,例如。
cslotty

8

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()。因此,即使使用a textarea也不足以确保此处的安全性。我建议不要将jQuery用于此任务,而应使用纯DOM API编写等效的代码。(是的,jQuery的旧行为令人发疯。)
Mark Amery

感谢您指出这一点。但是,该问题不包括检查脚本注入的要求。该问题专门询问有关Web服务器呈现的html的问题。保存到Web服务器的HTML内容在保存之前可能应该经过脚本注入验证。
杰森·威廉姆斯

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

1
-1; 在不受信任的输入上使用这是危险的不安全行为。例如,考虑stringtodecode包含的东西会发生什么<script>alert(1)</script>
Mark Amery

2

捕获常见问题的javascript解决方案:

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

这是https://stackoverflow.com/a/4835406/2738039的反面


如果您使用map[c] || ''无法识别的内容,则不会显示为undefined
Eldelshell

覆盖范围非常有限;-1。
Mark Amery

2
+1,更多的是unescapeHtml(str){ var map = {amp: '&', lt: '<', le: '≤', gt: '>', ge: '≥', quot: '"', '#039': "'"} return str.replace(/&([^;]+);/g, (m, c) => map[c]|| '') }
镇国寺HOAI新的2015年

手动覆盖。不建议。
Sergio A.

2

对于单线球员:

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

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

2

这个问题并没有指明来源,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。

(我最初将此内容发布为评论,但由于在同一主题中的后续评论要求我这样做,因此将其添加为答案)。


1

我尝试了一切从JSON数组中删除&的方法。以上示例都不是,但是https://stackoverflow.com/users/2030321/chris提供了一个很棒的解决方案,导致我解决了问题。

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

我没有使用,因为我不了解如何将其插入到将JSON数据拉入数组的模式窗口中,但是我确实根据示例进行了尝试,并且可以正常工作:

var modal = document.getElementById('demodal');
$('#ampersandcontent').text(replaceAll(data[0],"&amp;", "&"));

我喜欢它,因为它很简单并且可以使用,但是不确定为什么它没有被广泛使用。上下搜索以查找简单的解决方案。我将继续寻求对语法的理解,以及是否有使用此语法的风险。尚未发现任何东西。


您的第一个建议有些棘手,但无需付出很多努力即可很好地工作。另一方面,第二个仅使用蛮力解码字符。这意味着完成一个完整的解码功能可能要花费很多精力和时间。这就是为什么没有人使用这种方式来解决OP的问题。
塞尔吉奥·A。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.