Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding pollution of globals via iframe script loader?

Problem...

Poorly-coded scripts exist which need to be included on a web page.

These scripts pollute the global scope by doing things like:

  • Assigning values to undeclared identifiers
  • Adding properties to built-in constructor functions (like Object and Array) and their prototypes
  • Other nasty stuff.

Solution?

I want to include the scripts without the adverse side effects. I think it can be achieved by loading the script in an iframe and exporting objects as properties of the parent window. Here's what Ive got so far:

<script>

(function(){
  var g=this, frameIndex=frames.length, f=document.createElement('iframe');

  // hide it like this instead of display:none, because some old browser ignores
  // iframes with display:none, or is this an ancient habit I can drop?
  f.style.width='0px'; f.style.height='0px'; 
  f.style.border='none'; f.style.position='absolute';

  // append it to document.body or document.documentElement?
  // documentElement seems to work before body is loaded,
  // but is it cross-browser safe?  
  document.body.appendChild(f);

  // window object for our iframe
  var w=frames[frameIndex];

  // callback function pulls the object into the current window when script loads
  w.cb=function(){ g.SomeObject=w.SomeObject };

  // will this work on IE, or do I need to use document.createElement?
  // wanted to avoid document.createElement in this case because I'm not sure 
  // whether to call it from window.document or frames[frameIndex].document
  w.document.innerHTML='<script onload="cb()" src="myscript.js"><\/script>';

}());

</script>

Questions:

  • Will there be potential havoc if a script modifies built-in prototypes and I move it into another window, or will my parent window's built-ins stay clean and everything will "just work?"

  • Is this idea going to work on 'most' browsers, or is there a show-stopper? Haven't tested on anything besides chrome and moz so far.

  • I'd like to remove the iframe after pulling the object into the current window, but moz will lose the object reference if the iframe is removed. Does anyone know of a way around that?

  • Has this already been done, or is there a better way to accomplish my goal? If so, what's the name of the script or technique I should to be looking for?

(question transplanted from here)

like image 290
Dagg Nabbit Avatar asked Sep 30 '10 11:09

Dagg Nabbit


3 Answers

To copy a function you could cast it to a string and then eval it.... The code below also demonstrates that the iframe can be removed after doing this and your copy remains intact.

The following code sample using FF

Child.html snippet

<script>

//
// modify the prototype
//
Object.prototype.test = function(msg)
{
        alert(msg);
};  

//
// Simply declare a function
//
var whoo_hoo = function(){alert("whoo hoo");}
</script>

Parent with iframe:

 <iframe id="help_frame" src="http://localhost/child.html"
 onLoad="javascript:Help.import_functions(this)"></iframe>

    <script>
    var Help = {

          imported_function :null,
              import_functions : function(iframe)
   {
    this.imported_function = String(iframe.contentWindow.whoo_hoo);
    eval("this.imported_function = " + this.imported_function);
    iframe.parentNode.removeChild(iframe);

   //
   // displays 'whoo hoo' in an alert box
   //
   this.imported_function();

   try
   {
      //
      // If the Object prototype was changed in the parent
      // this would have displayed 'should not work' in an alert
      //
      this.test('should not work');
   }
   catch(e){alert('object prototype is unmodified');}

   }, 
    </script>

http://thecodeabode.blogspot.com/

like image 99
Ben Avatar answered Nov 20 '22 06:11

Ben


code for comment under Gabriel's answer..

var r = {
    init : null,
    _init: function(){
        var obj = new XMLHttpRequest();
        obj.onreadystatechange = function(){
            if ((this.status == 200) && this.readyState==4){
                try{
                    eval("r.init = function(){" + this.responseText + "}");
                    r.init();
                } catch(e){/*something bad in the script...*/}
            }
        }
        obj.open("GET","/jspolute_bad.js", true);
        obj.send();
    }   
}
r._init();

With methods being added to prototype, you might be in trouble if one or two of the exported functions expect the method as it is modified in foreign code. tedious solution that comes to mind is to Regex the responseText before eval'ing it for array.prototype,string.prototype and fix it some how. Will try that and let you know.. but it would mostly cater to straightforward scripts only.

like image 38
Ravindra Sane Avatar answered Nov 20 '22 05:11

Ravindra Sane


This could be a possible solution:

  • wrap all the foreign code into a class
  • make all the undeclared identifiers members of that class
  • before invoking the messy code, make a copy of the built-in classes and name them differently
    (is this possible??).

I think this should solve all the problems.

With my suggestion, your sample

var badA = "hahaha";
this.badB = "hehehe";
badC = "hohoho";

String.prototype.star = function(){ return '***' + this + '***' }

var somethingUseful = {
  doStuff: function () {
    alert((badA + badB + badC).star());
  }
}

should get like this

// Identifies the added properties to prototypes (ie String and Function)
// for later filtering if you need a for-in loop.
var stringAddons = [];
var functionAddons = []
var _string = new String();
var _function = function() {};
for (var property in _string) { if (!_string.hasOwnProperty(property)) { stringAddons.push(property); }}
for (var property in _function) { if (!_function.hasOwnProperty(property)) { functionAddons.push(property); }}

// Wraps the undeclared identifiers
var global = function()
{
  this.badA = "hahaha";
  this.badB = "hehehe";
  this.badC = "hohoho";

  String.prototype.star = function(){ return '***' + this + '***' }

  this.somethingUseful = {
    doStuff: function () {
      alert((global.badA + global.badB + global.badC).star());
    }
  }
}
var global = new Global();
global.somethingUseful.doStuff();

The tricky part is making ALL the undeclared identifiers global properties. Maybe a good regex script could make it. Im not that good with regex :)

like image 1
Gabriel Avatar answered Nov 20 '22 05:11

Gabriel