在JavaScript中呈现安全的HTML子集

时间:2012-07-03 11:30:54

标签: javascript html

作为markdown的替代方案,我正在寻找一种在JavaScript中安全地解析HTML的可配置子集的方法。

例如,对于

的(不可信)输入
<b onclick="alert('XSS');" data-myapp="asdf" style="color:red">Hello</b>
<h1><i style="color:expression(alert('XSS'));"> World</i></h1>

带参数

allowTags: b, i
allowAttrs: data-myapp
allowSafeStyle: color

我期待输出

<b data-myapp="asdf" style="color:red">Hello</b>
<i> World</i>

Markdown似乎无法表达更复杂的属性。 Caja似乎与我想要的非常接近,但需要服务器端渲染。那么,如何在JavaScript中呈现安全(根据上述参数allowTagsallowAttrs等)HTML的子集?

1 个答案:

答案 0 :(得分:1)

我使用jQuery使我的答案更短,并且包含更少的样板代码,但它不相关。

我正在使用.innerHTML,因为它不会在html中执行可能的脚本或css。

在这里演示http://jsfiddle.net/QCaGq/

function filterData(data, options ){
    var root;

    try {
        root = document.implementation.createHTMLDocument().body;
    }
    catch(e) {
        root = document.createElement("body");
    }

    root.innerHTML = data;

    $(root).find("*").filter(function(){
        return options.allowTags.indexOf(this.tagName.toLowerCase()) === -1;
    }).each( function() {
        $(this).children().detach().insertBefore( this );
        $(this).remove();
    });

    function removeStyle( node, attr ) {
        var style = node.style,
            prop,
            name,
            len = style.length,
            i, val = "";

        for( i = 0; i < len; ++i ) {
            name = style[i];
            prop = style[name];

            if( options.allowSafeStyle.indexOf( name ) > -1 ) {
                val += (name + ":" + prop + ";");
            }
        }

        if( val ) {
            attr.nodeValue = val;
        }
        else {
            node.removeAttribute("style");
        }
    }

    function removeAttrs( node ) {
        $.each( node.attributes, function( index, attr ) {

            if( !attr ) {
                return;
            }

            if( attr.name.toLowerCase() === "style" ) {
                return removeStyle( node, attr );
            }

            if( options.allowAttrs.indexOf(attr.name.toLowerCase()) === -1 ) {
                node.removeAttribute(attr.name);
            }
        });
    }

    function walk( root ) {
        removeAttrs(root);
        $( root.childNodes ).each( function() {
            if( this.nodeType === 8 ) { //Remove html comments
                $(this).remove();
            }
            else if( this.nodeType === 1 ) {
                walk(this);
            }
        });
    }

    walk(root);

    return root.innerHTML; 
}

var opts = {
    allowTags: ["b", "i"],
    allowAttrs: ["data-myapp"],
    allowSafeStyle: ["color"]
}

filterData( '<b onclick="alert(\'XSS\');" data-myapp="asdf" style="color:red">Hello</b>\n<h1><i style="color:expression(alert(\'XSS\'));"> World</i></h1>', opts );

结果:

<b data-myapp="asdf" style="color:red;">Hello</b>
<i> World</i>

这应该让你开始。