すべての主要なブラウザがサンドボックス化されたiframeをサポートするようになったので、安全であると私が考えるはるかに簡単な方法があります。この種のセキュリティ問題に精通している人々がこの回答をレビューできれば、私はそれが大好きです。
注:この方法は、IE9以前では確実に機能しません。サンドボックスをサポートするブラウザのバージョンについては、この表を参照してください。(注:表には、Opera Miniでは機能しないと書かれているようですが、試してみたところ、機能しました。)
アイデアは、JavaScriptを無効にして非表示のiframeを作成し、信頼できないHTMLを貼り付けて、解析させることです。次に、DOMツリーをたどって、安全と見なされるタグと属性をコピーします。
ここに示されているホワイトリストは単なる例です。ホワイトリストに登録するのに最適なものは、アプリケーションによって異なります。タグと属性のホワイトリストだけでなく、より高度なポリシーが必要な場合は、このサンプルコードではなく、このメソッドで対応できます。
var tagWhitelist_ = {
'A': true,
'B': true,
'BODY': true,
'BR': true,
'DIV': true,
'EM': true,
'HR': true,
'I': true,
'IMG': true,
'P': true,
'SPAN': true,
'STRONG': true
};
var attributeWhitelist_ = {
'href': true,
'src': true
};
function sanitizeHtml(input) {
var iframe = document.createElement('iframe');
if (iframe['sandbox'] === undefined) {
alert('Your browser does not support sandboxed iframes. Please upgrade to a modern browser.');
return '';
}
iframe['sandbox'] = 'allow-same-origin';
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.contentDocument.body.innerHTML = input;
function makeSanitizedCopy(node) {
if (node.nodeType == Node.TEXT_NODE) {
var newNode = node.cloneNode(true);
} else if (node.nodeType == Node.ELEMENT_NODE && tagWhitelist_[node.tagName]) {
newNode = iframe.contentDocument.createElement(node.tagName);
for (var i = 0; i < node.attributes.length; i++) {
var attr = node.attributes[i];
if (attributeWhitelist_[attr.name]) {
newNode.setAttribute(attr.name, attr.value);
}
}
for (i = 0; i < node.childNodes.length; i++) {
var subCopy = makeSanitizedCopy(node.childNodes[i]);
newNode.appendChild(subCopy, false);
}
} else {
newNode = document.createDocumentFragment();
}
return newNode;
};
var resultElement = makeSanitizedCopy(iframe.contentDocument.body);
document.body.removeChild(iframe);
return resultElement.innerHTML;
};
ここで試してみることができます。
この例では、スタイル属性とタグを許可していないことに注意してください。それらを許可した場合は、CSSを解析して、目的に対して安全であることを確認することをお勧めします。
私はこれをいくつかの最新のブラウザー(Chrome 40、Firefox 36 Beta、IE 11、Chrome for Android)でテストし、1つの古いブラウザー(IE 8)でテストして、スクリプトを実行する前に正常に動作することを確認しました。問題のあるブラウザや、見落としているエッジケースがあるかどうかを知りたいと思います。