3

作为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 中呈现 HTML 的安全子集(根据上述参数allowTags,allowAttrs等)?

4

1 回答 1

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>

这应该让你开始。

于 2012-07-03T12:04:02.213 回答