Search code examples
javascriptiframesandbox

Editable iframe without allowing JavaScript to run


I have an iframe:

<iframe id="msgContainer" sandbox="allow-same-origin"></iframe>

and I'd like to insert HTML into it, but not let any JavaScript that may be contained in the HTML run (this includes both <script> tags and on* attributes. I know how to insert HTML (just use document.getElementById('msgContainer').contentDocument.body.innerHTML=myHTML but I'd like to prevent any JS in myHTML from running. The way I've tried to do this is by using the sandbox attribute and only allowing same-origin, but JS still runs. Is there any way to do this?

Thanks


Solution

  • I couldn't find any answer other than to parse out the JS from an html string inserted into the iframe. Here's my code (if it helps anyone else):

    /** Removes javascript from html string
     * html: the string to be cleaned
    */
    function clean(html) {
        function stripHTML(){
            html = html.slice(0, strip) + html.slice(j);
            j = strip;
            strip = false;
        }
    
        var strip = false,
        lastQuote = false,
        tag = false;
        const prefix = "ANYTHING",
        sandbox = " sandbox=''";
    
        for(var i=0; i<html.length; i++){
            if(html[i] === "<" && html[i+1] && isValidTagChar(html[i+1])) {
                i++;
                tag = false;
                /* Enter element */
                for(var j=i; j<html.length; j++){
                    if(!lastQuote && html[j] === ">"){
                        if(strip) {
                            stripHTML();
                        }
                        /* sandbox iframes */
                        if(tag === "iframe"){
                            var index = html.slice(i, j).toLowerCase().indexOf("sandbox");
                            if(index > 0) {
                                html = html.slice(0, i+index) + prefix + html.slice(i+index);
                                j += prefix.length;
                            }
                            html = html.slice(0, j) + sandbox + html.slice(j);
                            j += sandbox.length;
                        }
                        i = j;
                        break;
                    }
                    if(!tag && html[j] === " "){
                        tag = html.slice(i, j).toLowerCase();
                    }
                    if(lastQuote === html[j]){
                        lastQuote = false;
                        continue;
                    }
                    if(!lastQuote && html[j-1] === "=" && (html[j] === "'" || html[j] === '"')){
                        lastQuote = html[j];
                    }
                    /* Find on statements */
                    if(!lastQuote && html[j-2] === " " && html[j-1] === "o" && html[j] === "n"){
                        strip = j-2;
                    }
                    if(strip && html[j] === " " && !lastQuote){
                        stripHTML();
                    }
                }
            }
        }
        html = stripScripts(html);
        return html;
    }
    
    /** Returns whether or not the character is a valid first character in a tag
     * str: the first character
    */
    function isValidTagChar(str) {
        return str.match(/[a-z?\\\/!]/i);
    }
    
    /** Strips scripts from a string of html
     * html: the string of html to be stripped
    */
    // NOTE: <script> tags won't run in this context
    function stripScripts(html) {
        var div = document.createElement('div');
        div.innerHTML = html;
        var scripts = div.getElementsByTagName('script');
        var i = scripts.length;
        while (i--) {
          scripts[i].parentNode.removeChild(scripts[i]);
        }
        return div.innerHTML;
    }