将HTML标签作为HTML实体转义的最快方法?


98

我正在写一个Chrome扩展程序,包括做了很多以后的工作中:消毒的字符串可能包含HTML标签,通过转换<>&&lt;&gt;&amp;分别。

(换句话说,与PHP相同htmlspecialchars(str, ENT_NOQUOTES)-我认为并不需要转换双引号字符。)

这是到目前为止我发现的最快的功能:

function safe_tags(str) {
    return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') ;
}

但是,当我不得不一次性运行数千个字符串时,仍然存在很大的滞后。

任何人都可以对此进行改进吗?如果有所不同,则通常用于10到150个字符之间的字符串。

(我曾经想到的一个想法是不必费心编码大于号-这样会不会有真正的危险?)


2
为什么?在大多数情况下,您想要将数据插入DOM,在这种情况下,您应该忘记转义数据,而仅使用它创建一个textNode。
昆汀

1
@David Dorward:也许他想清理POST数据,但服务器不能正确往返数据。
Lie Ryan

4
@Lie —如果是这样,则解决方案是“为Pete着想,请在有较大XSS漏洞的情况下修复服务器”
Quentin

2
@David Dorward:这种情况很可能是他无法控制服务器。最近,我遇到了这样的情况:我正在编写一个Oilmonkey脚本来解决我在大学网站上不喜欢的几件事。我必须在无法控制的服务器上执行POST,并使用javascript清理POST数据(因为原始数据来自富文本框,因此还有html标记堆,它们无法在服务器上往返) 。网络管理员无视我要求他们修复网站的要求,因此我别无选择。
Lie Ryan

1
我有一个用例,需要在div中显示错误消息。该错误信息可以包含HTML和换行符。我想转义HTML并将换行符替换为<br>。然后将结果放入div中进行显示。
mozey

Answers:


83

您可以尝试传递回调函数来执行替换:

var tagsToReplace = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;'
};

function replaceTag(tag) {
    return tagsToReplace[tag] || tag;
}

function safe_tags_replace(str) {
    return str.replace(/[&<>]/g, replaceTag);
}

这是一个性能测试:http : //jsperf.com/encode-html-entities以与replace重复调用该函数以及使用Dmitrij提出的DOM方法进行比较。

你的方法似乎更快...

但是,为什么需要它?


2
没有必要逃脱>

6
实际上,如果将转义的值放在html元素的属性中,则需要转义>符号。否则,它将破坏该html元素的标签。
Zlatin Zlatev

1
在普通文本中,转义字符很少。如果您关心最大速度,最好只在需要时才调用replace:if (/[<>&"]/.test(str) { ... }
Vitaly 2014年

3
@callum:不。我对列举“可能会出问题的案例”不感兴趣(尤其是因为意外/被遗忘的案例会伤害您,以及您最不希望出现这种情况)。我对按标准编码感兴趣(因此,意外的/遗忘的情况从定义上来说不会伤害您)。我不能强调这有多重要。>是HTML中的特殊字符,因此请对其进行转义。就那么简单。:)
轻轨赛将于

4
@LightnessRacesinOrbit这很重要,因为问题是最快的方法是什么。如果可以跳过>替换,那将使其更快。
卡鲁姆,2015年

103

这是您可以执行此操作的一种方法:

var escape = document.createElement('textarea');
function escapeHTML(html) {
    escape.textContent = html;
    return escape.innerHTML;
}

function unescapeHTML(html) {
    escape.innerHTML = html;
    return escape.textContent;
}

这是一个演示。


重新设计了演示。这是全屏版本:jsfiddle.net/Daniel_Hug/qPUEX/show/light
Web_Designer 2013年

13
不知道如何/为什么/为什么-但这是天才。
rob_james 2014年

4
看起来好像是在利用TextArea元素的现有代码来转义文字文本。很好,我认为这个小技巧将找到另一个家。
Ajax

3
@jazkat我没有使用该功能。我使用的转义变量在示例中定义。
Web_Designer

2
但这会丢失空白
安德鲁(Andrew)

31

Martijn的方法作为原型函数:

String.prototype.escape = function() {
    var tagsToReplace = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;'
    };
    return this.replace(/[&<>]/g, function(tag) {
        return tagsToReplace[tag] || tag;
    });
};

var a = "<abc>";
var b = a.escape(); // "&lt;abc&gt;"

12
String像这样添加它应该是escapeHtml,因为它通常不是String的转义。这是String.escapeHtml正确的,但String.escape提出了一个问题,“逃什么?”
劳伦斯·多尔2014年

3
是的,好主意。这些天来,我已经避免扩展原型以避免冲突。
Aram Kocharyan 2014年

1
如果您的浏览器支持Symbol,则可以使用它来避免污染字符串键名称空间。var escape = new Symbol(“ escape”); String.prototype [escape] = function(){...}; “ text” [escape]();
Ajax

12

更快或更短的解决方案是:

escaped = new Option(html).innerHTML

这与JavaScript的一些怪异痕迹有关,其中Option元素保留了一个自动进行这种转义的构造函数。

归功于https://github.com/jasonmoo/t.js/blob/master/t.js


1
整洁的单线但正则表达式之后最慢的方法。另外,根据规范
ShortFuse

请注意,@ ShortFuse的“最慢方法”链接使我的系统用尽了RAM(约有6GB可用空间),firefox似乎在内存不足之前就停止了分配,因此Linux会坐在那里让您做,而不是杀死令人讨厌的进程硬断电。
吕克(Luc)

11

AngularJS源代码在angular-sanitize.js内部也有一个版本。

var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
    // Match everything outside of normal chars and " (quote character)
    NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
/**
 * Escapes all potentially dangerous characters, so that the
 * resulting string can be safely inserted into attribute or
 * element text.
 * @param value
 * @returns {string} escaped text
 */
function encodeEntities(value) {
  return value.
    replace(/&/g, '&amp;').
    replace(SURROGATE_PAIR_REGEXP, function(value) {
      var hi = value.charCodeAt(0);
      var low = value.charCodeAt(1);
      return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
    }).
    replace(NON_ALPHANUMERIC_REGEXP, function(value) {
      return '&#' + value.charCodeAt(0) + ';';
    }).
    replace(/</g, '&lt;').
    replace(/>/g, '&gt;');
}

1
哇,这个非字母正则表达式很激烈。我认为| 在表达式中是必需的。
Ajax


9

多合一脚本:

// HTML entities Encode/Decode

function htmlspecialchars(str) {
    var map = {
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        "\"": "&quot;",
        "'": "&#39;" // ' -> &apos; for XML only
    };
    return str.replace(/[&<>"']/g, function(m) { return map[m]; });
}
function htmlspecialchars_decode(str) {
    var map = {
        "&amp;": "&",
        "&lt;": "<",
        "&gt;": ">",
        "&quot;": "\"",
        "&#39;": "'"
    };
    return str.replace(/(&amp;|&lt;|&gt;|&quot;|&#39;)/g, function(m) { return map[m]; });
}
function htmlentities(str) {
    var textarea = document.createElement("textarea");
    textarea.innerHTML = str;
    return textarea.innerHTML;
}
function htmlentities_decode(str) {
    var textarea = document.createElement("textarea");
    textarea.innerHTML = str;
    return textarea.value;
}

http://pastebin.com/JGCVs0Ts


我没有拒绝投票,但是所有的正则表达式样式替换都无法对unicode进行编码...因此,使用外语的任何人都会感到失望。上面提到的<textarea>技巧确实很酷,可以快速安全地处理所有内容。
Ajax

regex对我来说可以与许多非拉丁Unicode字符一起正常工作。我什么也没期待。您怎么认为这行不通?您是否在考虑需要HTML实体的单字节代码页?这就是第三和第四函数的作用,而显然不是第一和第二函数。我喜欢差异化。
ygoe '16

@LonelyPixel我不认为他会在您不提及他的情况下看到您的评论(“只能通知一个额外的用户;该职位的所有者将始终得到通知”)
baptx 2016年

我根本不知道有针对性的通知。@Ajax,请参阅上面的评论。
ygoe '16

我现在看到的@LonelyPixel。由于某种原因,我认为此答案中没有textarea样式替换。的确,我确实在考虑像普通话这样的双码点大unicode值。我的意思是,有可能使正则表达式足够智能,但是当您查看浏览器供应商可以采用的快捷方式时,我会很好地打赌textarea的速度会快得多(比完全称职的正则表达式要快)。有人针对此答案发布了基准吗?我发誓我见过一个。
阿贾克斯

2

function encode(r) {
  return r.replace(/[\x26\x0A\x3c\x3e\x22\x27]/g, function(r) {
	return "&#" + r.charCodeAt(0) + ";";
  });
}

test.value=encode('How to encode\nonly html tags &<>\'" nice & fast!');

/*
 \x26 is &ampersand (it has to be first),
 \x0A is newline,
 \x22 is ",
 \x27 is ',
 \x3c is <,
 \x3e is >
*/
<textarea id=test rows=11 cols=55>www.WHAK.com</textarea>


1

我不太确定速度,但是如果您正在寻找简单性,我建议您使用lodash /下划线 转义功能。


0

Martijn的方法是带有“”标记的单个函数(在javascript中使用):

function escapeHTML(html) {
    var fn=function(tag) {
        var charsToReplace = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&#34;'
        };
        return charsToReplace[tag] || tag;
    }
    return html.replace(/[&<>"]/g, fn);
}

0

我会补充XMLSerializer。它提供了最快的结果,而无需使用任何对象缓存(不在序列化器上,也没有在Text节点上)。

function serializeTextNode(text) {
  return new XMLSerializer().serializeToString(document.createTextNode(text));
}

额外的好处是它支持与文本节点不同的序列化属性:

function serializeAttributeValue(value) {
  const attr = document.createAttribute('a');
  attr.value = value;
  return new XMLSerializer().serializeToString(attr);
}

您可以通过检查规范(文本节点属性值)来查看其实际替换的内容。完整的文档具有更多的节点类型,但是概念是相同的。

至于性能,如果不缓存,它是最快的。当您确实允许缓存时,innerHTML以子Text节点调用HTMLElement最快。正则表达式将是最慢的(如其他评论所证明)。当然,XMLSerializer在其他浏览器上可能会更快,但是在我的(有限的)测试中,a innerHTML是最快的。


最快的单行:

new XMLSerializer().serializeToString(document.createTextNode(text));

最快的缓存:

const cachedElementParent = document.createElement('div');
const cachedChildTextNode = document.createTextNode('');
cachedElementParent.appendChild(cachedChildTextNode);

function serializeTextNode(text) {
  cachedChildTextNode.nodeValue = text;
  return cachedElementParent.innerHTML;
}

https://jsperf.com/htmlentityencode/1


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.