小提琴 :http :
//jsfiddle.net/JFSKe/6/
documentFragment
没有实现DOM方法。使用
document.createElement与结合
innerHTML去除
<head>和
<body>标签(即使当创建的元素是一个根元素,
<html>)。因此,应该在其他地方寻求解决方案。我创建了一个
跨浏览器的 字符串到DOM函数,该函数利用了不可见的嵌入式框架。
所有外部资源和脚本将被禁用。有关更多信息,请参见 代码说明 。
码
function string2dom(html, callback){ html = sanitiseHTML(html); var iframe = document.createElement("iframe"); iframe.style.display = "none"; document.body.appendChild(iframe); var doc = iframe.contentdocument || iframe.contentWindow.document; doc.open(); doc.write(html); doc.close(); function destroy(){ iframe.parentNode.removeChild(iframe); } if(callback) callback(doc, destroy); else return {"doc": doc, "destroy": destroy};}function sanitiseHTML(html){ var prefix = "<!--"'-->"; var att = "[^-a-z0-9:._]"; var tag = "<[a-z]"; var any = "(?:[^<>"']*(?:"[^"]*"|'[^']*'))*?[^<>]*"; var etag = "(?:>|(?=<))"; var entityEnd = "(?:;|(?!\d))"; var ents = {" ":"(?:\s| ?|�*32"+entityEnd+"|�*20"+entityEnd+")", "(":"(?:\(|�*40"+entityEnd+"|�*28"+entityEnd+")", ")":"(?:\)|�*41"+entityEnd+"|�*29"+entityEnd+")", ".":"(?:\.|�*46"+entityEnd+"|�*2e"+entityEnd+")"}; var charMap = {}; var s = ents[" "]+"*"; function ae(string){ var all_chars_lowercase = string.toLowerCase(); if(ents[string]) return ents[string]; var all_chars_uppercase = string.toUpperCase(); var RE_res = ""; for(var i=0; i<string.length; i++){ var char_lowercase = all_chars_lowercase.charAt(i); if(charMap[char_lowercase]){ RE_res += charMap[char_lowercase]; continue; } var char_uppercase = all_chars_uppercase.charAt(i); var RE_sub = [char_lowercase]; RE_sub.push("�*" + char_lowercase.charCodeAt(0) + entityEnd); RE_sub.push("�*" + char_lowercase.charCodeAt(0).toString(16) + entityEnd); if(char_lowercase != char_uppercase){ RE_sub.push("�*" + char_uppercase.charCodeAt(0) + entityEnd); RE_sub.push("�*" + char_uppercase.charCodeAt(0).toString(16) + entityEnd); } RE_sub = "(?:" + RE_sub.join("|") + ")"; RE_res += (charMap[char_lowercase] = RE_sub); } return(ents[string] = RE_res); } function by(match, group1, group2){ return group1 + "data-" + group2 } function cr(selector, attribute, marker, delimiter, end){ if(typeof selector == "string") selector = new RegExp(selector, "gi"); marker = typeof marker == "string" ? marker : "\s*="; delimiter = typeof delimiter == "string" ? delimiter : ""; end = typeof end == "string" ? end : ""; var is_end = end && "?"; var re1 = new RegExp("("+att+")("+attribute+marker+"(?:\s*"[^""+delimiter+"]*"|\s*'[^'"+delimiter+"]*'|[^\s"+delimiter+"]+"+is_end+")"+end+")", "gi"); html = html.replace(selector, function(match){ return prefix + match.replace(re1, by); }); } function cri(selector, attribute, front, flags, delimiter, end){ if(typeof selector == "string") selector = new RegExp(selector, "gi"); flags = typeof flags == "string" ? flags : "gi"; var re1 = new RegExp("("+att+attribute+"\s*=)((?:\s*"[^"]*"|\s*'[^']*'|[^\s>]+))", "gi"); end = typeof end == "string" ? end + ")" : ")"; var at1 = new RegExp('(")('+front+'[^"]+")', flags); var at2 = new RegExp("(')("+front+"[^']+')", flags); var at3 = new RegExp("()("+front+'(?:"[^"]+"|'[^']+'|(?:(?!'+delimiter+').)+)'+end, flags); var handleAttr = function(match, g1, g2){ if(g2.charAt(0) == '"') return g1+g2.replace(at1, by); if(g2.charAt(0) == "'") return g1+g2.replace(at2, by); return g1+g2.replace(at3, by); }; html = html.replace(selector, function(match){ return prefix + match.replace(re1, handleAttr); }); } html = html.replace(new RegExp("<meta"+any+att+"http-equiv\s*=\s*(?:""+ae("refresh")+"""+any+etag+"|'"+ae("refresh")+"'"+any+etag+"|"+ae("refresh")+"(?:"+ae(" ")+any+etag+"|"+etag+"))", "gi"), "<!-- meta http-equiv=refresh stripped-->"); html = html.replace(new RegExp("<script"+any+">\s*//\s*<\[CDATA\[[\S\s]*?]]>\s*</script[^>]*>", "gi"), "<!--CDATA script-->"); html = html.replace(/<script[Ss]+?</scripts*>/gi, "<!--Non-CDATA script-->"); cr(tag+any+att+"on[-a-z0-9:_.]+="+any+etag, "on[-a-z0-9:_.]+"); cr(tag+any+att+"href\s*="+any+etag, "href"); cr(tag+any+att+"src\s*="+any+etag, "src"); cr("<object"+any+att+"data\s*="+any+etag, "data"); cr("<applet"+any+att+"prebase\s*="+any+etag, "prebase"); cr("<param"+any+att+"name\s*=\s*(?:""+ae("movie")+"""+any+etag+"|'"+ae("movie")+"'"+any+etag+"|"+ae("movie")+"(?:"+ae(" ")+any+etag+"|"+etag+"))", "value"); cr(/<style[^>]*>(?:[^"']*(?:"[^"]*"|'[^']*'))*?[^'"]*(?:</style|$)/gi, "url", "\s*\(\s*", "", "\s*\)"); cri(tag+any+att+"style\s*="+any+etag, "style", ae("url")+s+ae("(")+s, 0, s+ae(")"), ae(")")); cr(/<style[^>]*>(?:[^"']*(?:"[^"]*"|'[^']*'))*?[^'"]*(?:</style|$)/gi, "expression", "\s*\(\s*", "", "\s*\)"); cri(tag+any+att+"style\s*="+any+etag, "style", ae("expression")+s+ae("(")+s, 0, s+ae(")"), ae(")")); return html.replace(new RegExp("(?:"+prefix+")+", "g"), prefix);}代码说明
该
sanitiseHTML函数基于我的
replace_all_rel_by_abs函数(请参阅此答案)。
sanitiseHTML不过,该功能已完全重写,以实现最大的效率和可靠性。
此外,添加了一组新的RegExps以删除所有脚本和事件处理程序(包括CSS
expression(),IE7-)。为确保所有标签均按预期进行解析,已调整标签的前缀为
<!--'"-->。要正确解析嵌套的“事件处理程序”以及未终止的引号,该前缀是必需的
<aid="><input onclick="<div onmousemove=evil()>">。
这些正则表达式是使用内部函数动态创建
cr/
cri( ç reate ř E放置[ 我
n第])。这些函数接受参数列表,并创建和执行高级RE替换。为了确保HTML实体没有违反一个RegExp(
refresh在
<meta http-equiv=refresh>可以用各种方式来写的),动态创建的正则表达式的一部分被构造函数
ae( 一个 纽约 ê ntity)。
实际的替换是按功能完成的
by(替换 为 )。在此实现中,在所有匹配的属性之前
by添加
data-。
- 所有
<script>//<[CDATA[ .. //]]></script>
出现的条带化。此步骤是必需的,因为CDATA
节允许</script>
代码内包含字符串。执行此替换后,可以安全地进行下一个替换: - 其余
<script>...</script>
标签将被删除。 - 该
<meta http-equiv=refresh .. >
标记将被删除 所有 的事件侦听器和外部指针/属性(
href
,src
,url()
)由前缀data-
,如先前所描述。Iframe
创建一个对象。iframe不太可能泄漏内存(与htmlfile ActiveXObject相反)。Iframe变得不可见,并附加到文档中,以便可以访问DOM。document.write()
用于将HTML写入Iframe。document.open()
和document.close()
用于清空文档的先前内容,以便生成的文档是给定html
字符串的精确副本。如果指定了回调函数,则将使用两个参数来调用该函数。第 一个 参数是对所生成
document
对象的引用。该 第二 参数是一个函数被调用时它破坏所生成的DOM树。当您不再需要树时,应调用此函数。
如果未指定回调函数,则该函数将返回一个由两个属性(doc
和destroy
)组成的对象,这两个属性的行为与前面提到的参数相同。
补充笔记
- 将
designMode
属性设置为“开”将阻止框架执行脚本(Chrome不支持)。如果<script>
由于特定原因必须保留标签,则可以使用iframe.designMode = "On"
代替脚本剥离功能。 我找不到可靠的来源
htmlfile activeXObject
。根据此消息来源,htmlfile
它比Iframe速度慢,并且更容易受到内存泄漏的影响。所有受影响的属性(
href
,,src
…)都以前缀data-
。获得/改变这些属性中的一个例子示出了用于data-href
:elem.getAttribute("data-href")和elem.setAttribute("data-href", "...")elem.dataset.href
和elem.dataset.href = "..."
。外部资源已被禁用。结果,页面看起来可能完全不同: 没有外部样式 没有脚本样式 没有图像:元素的大小可能完全不同。
<link rel="stylesheet" href="main.css" />
<script>document.body.bgColor="red";</script>
<img src="128x128.png" />
__
例子
sanitiseHTML(html)
将此书签粘贴到位置栏中。它将提供一个选项来插入文本区域,以显示经过清理的HTML字符串。
javascript:void(function(){var s=document.createElement("script");s.src="http://rob.lekensteyn.nl/html-sanitizer.js";document.body.appendChild(s)})();代码示例-string2dom(html)
:
string2dom("<html><head><title>Test</title></head></html>", function(doc, destroy){ alert(doc.title); destroy();});var test = string2dom("<div id='secret'></div>");alert(test.doc.getElementById("secret").tagName); test.destroy();著名参考
- 所以:JS RE更改所有相对于绝对URL的 -函数
sanitiseHTML(html)
基于我之前创建的replace_all_rel_by_abs(html)
函数。 - 元素-嵌入内容 -标准嵌入元素的完整列表
- 元素-先前的HTML元素 -(已弃用)元素的附加列表(例如
<applet>
) - htmlfile ActiveX对象 - “比iframe沙箱慢。如果不受管理,则泄漏内存”



