Accessing Grandparent's variable - javascript

I have an Object with the variable "parentVar", and I want to write the response of an ajax-request into it.
Sounds easy, but I'm not able to do this, due to some namespace-troubles.
I wrote a very minimalistic example, which shows the problem.
The ajax-request will be started, when I call the Object's init-Function:
var parentObj = new Parent();
//Do some work (I need some functionality from the object before it can be fully initialized)
alert("Old Value = " + parentObj.parentVar);
parentObj.init(function(){
alert("New Value = " + parentObj.parentVar);
});
The init-Function calls the Function "load" which performs an Ajax-Request, and returns the received data (in another callback-Function).
function Parent(){
this.parentVar = "old"; //this value should be replaced
this.init = function(initCallBack){
this.load(function(newData){ //load the content of the file
this.parentVar = newData; //write the content into the variable
initCallBack(); //I'm done!
});
}
this.load = function(callbackFunc){
//load the new value from a file via ajax (asyncron).
//When finished: call callbackFunc
callbackFunc("newValue");
}
}
I already tried passing the scope to the loader-function, and getting it back in the callBack-Function. But it didn't work.
I also tried "var parentscope = this;" in the init-Function,
and "parentscope.parentVar = newData;" - it didn't work either.
Is it possible to achieve this with parentVar being private? (I mean "var parentVar = 'old';" instead of "this.parentVar = 'old';").

The callback function passed to load() doesn't have the value of this that you want. The way you call that function this will be window. You can bind the value of this as follows:
this.load(function(newData){ //load the content of the file
this.parentVar = newData; //write the content into the variable
initCallBack(); //I'm done!
}.bind(this));
...and then it will work: http://jsfiddle.net/v9Hvb/
Is it possible to achieve this with parentVar being private? (I mean var parentVar = 'old'; instead of this.parentVar = 'old';).
You can use a private variable inside the Parent() constructor and it will be accessible within all of the methods defined inside the constructor. But it won't be accessible outside with parentObj.parentVar so you'd have to add a getter method.
It would be easier to use a private var self = this; variable inside the Parent() constructor and then use self instead of this in the methods:
function Parent(){
var self = this;
self.parentVar = "old"; //this value should be replaced
self.init = function(initCallBack){
self.load(function(newData){ //load the content of the file
self.parentVar = newData; //write the content into the variable
initCallBack(); //I'm done!
});
}
self.load = function(callbackFunc){
//load the new value from a file via ajax (asyncron).
//When finished: call callbackFunc
callbackFunc("newValue");
}
}
Demo: http://jsfiddle.net/v9Hvb/1/
Further reading: MDN's this article

The problem is that the this within the callback passed to the load within the init function doesn't refer to the Parent object but the window object. There is a well known hack for this: save the reference to the Parent's this in _this. Other variable names commonly used are that and self and all these prefixed with more underscores. Here is a code that works:
function Parent(){
this.parentVar = "old"; //this value should be replaced
var _this = this;
this.init = function(initCallBack){
this.load(function(newData){ //load the content of the file
_this.parentVar = newData; //write the content into the variable
initCallBack(); //I'm done!
});
}
this.load = function(callbackFunc){
//load the new value from a file via ajax (asyncron).
//When finished: call callbackFunc
callbackFunc("newValue");
}
}

Related

Block access to certain variables within function

How can I make a function which was originally defined elsewhere in my code be denied or limited access to certain objects when it gets assigned to an object?
Short Example
var func = function() {
console.log(window.screenX); // <-- this should work
document.createElement("div"); // <-- this should fail (ideally in
// some graceful way)
};
var makeObj = function(pFunc) {
var window = {
"screenX": window.screenX
};
var document = null;
var rObj = {};
rObj.func = pFunc;
return rObj;
};
var obj = makeObj(func);
obj.func(); // <-- want it to have no access to document and
// limited access to window
func is defined by privileged users as a sort of plugin but I want to limit what they can do such as not creating new elements but still be able to access certain variables and functions they will need (such as window.screenX)
I'm sure that there are tons of possible answers, my answer make use of the Function-constructor so that window and document are defined locally within the scope of an also locally defined function. This is very important because you do not want to overwrite both objects globally.
But lets come to this answer. Within makeObj all allowed objects in func are defined with the first and second param of a new Function-Constructor.
The third param is used to create an "anonymous" function that contains certain assignments to those allowed objects. These assignments were created locally by using serializeFakeObject.
In other words, the first and second param with new Function are going to be the params (header or signature) while the third param is going to be the body of the „anonymous“ function.
Please note that this "anonymous" function is created dynamically and looks like this afterwards:
function anonymous(window,document) {
// overwrites allowed objects
window = {"screenX":-8};
document = {};
document.createElement = function (){ throw Error("document.createElement is not allowed"); };
// this function must take assignments above
// no chance to use the globally defined objects window or document
return function (){
console.log(window);
console.log(document.createElement());
}();
}
This anonymous function is dynamically created within makeObj and after that executed by calling obj.func() at the end.
Lets put all together:
var func = function (){
console.log(window); // has just got screenX
console.log(document.createElement()); // throws an error "document.createElement is not allowed"
};
var makeObj = function(pFunc) {
var serializeFakeObject = function(name,obj){
var serializedObject = "";
// when its a property of type function then omit JSON.stringify otherwise it gets undefined
if(obj.__proto__.constructor === Function)serializedObject = obj;
else serializedObject = JSON.stringify(obj);
return name + ' = ' + serializedObject + ';';
};
var fakeWindow = serializeFakeObject("window", { "screenX": window.screenX });
var fakeDocument = serializeFakeObject("document", {});
// create property createElement of document that should fail in a graceful way
var fakeDocumentProperty = serializeFakeObject("document.createElement", function (){ throw Error("document.createElement is not allowed"); });
// if you like you can add more properties here....
var concatFakeObjects = fakeWindow+fakeDocument+fakeDocumentProperty;
var rObj = {};
// the line below creates the "anonymous" function described above
rObj.func = new Function('window', 'document', concatFakeObjects +'return '+pFunc+'();');
return rObj;
};
var obj = makeObj(func);
console.log(obj.func());

How to access jquery library variable from public function?

I'm creating simple jquery library, actually I just learn about creating my own library with jQuery, I need to access variable inside lib function from outside main function. Here short code, I want to access variable page and loading from $.fn.beyondScroll.reset function. How I accomplish this task? I have tried to access via $.fn.beyondScroll.page; but it return undefinded. Thanks.
(function ($) {
$.fn.beyondScroll = function (options) {
var settings = $.extend({}, $.fn.beyondScroll.defaults, options);
var complete = false; // need access this variable
var page = 1;
return this.each(function () {
var loading = false; // and this variable
});
};
$.fn.beyondScroll.reset = function(){
// access those variables from here
var currentPage = $.fn.beyondScroll.page; // return undefined
}
}(jQuery));

Accessing 'this' function from within the same function

I wrote some javascript to simulate a namespace. The following global allows me to access the get_cp_span() getter method from other namespaces:
var my_ns; // global access
$(document).ready(function() {
my_ns = new my_namespace(); // need global access to my_ns
});
function my_namespace() {
var cp_span = $("#cp_span");
// public getter
this.get_cp_span = function() {
return cp_span;
}
var do_something = function() {
//get_cp_span().hide(); // ReferenceError: get_cp_span is not defined
//this.get_cp_span().hide(); // TypeError: this.get_cp_span is not a function
my_ns.get_cp_span().hide(); // works
}
};
I thought that I would be able to easily access my public getter cp_span() from within the my_namespace() by calling this.get_cp_span() or by just calling get_cp_span() but it only works when I call my_ns.get_cp_span() which is how I would call it from a different 'namespace'.
In order for me to access get_cp_span() from within the same function, I have to refer to the global variable.
new creates a plain object, inside my_namespace this points to the newly created object, you can save this object inside another private variable so that any function declared inside my_namespace have access to it, I've seen that this variable is commonly named as self or me but you can name it whatever you want
var my_ns; // global access
$(document).ready(function() {
my_ns = new my_namespace(); // need global access to my_ns
});
function my_namespace() {
// save the newly created object
var me = this;
var cp_span = $("#cp_span");
// public getter
this.**get_cp_span** = function() {
return cp_span;
}
var do_something = function() {
me.get_cp_span().hide();
}
};
Note that if you call my_namespace multiple times, me will refer to each instance created, I'd avoid using new though since I think that the namespace should be unique right? I'd be better to use the singleton pattern

I don't understand why the "this" keyword doesn't work as I expect

What I want to do is to execute the create_tag function when a specified condition is satisfied. I am referring to this function as a method of an object, in this case document.body, by setting as its method an external function, "create_tag(..)". The problem is inside this function I have a "this" keyword which I would expect to refer to the method's parent, document.body. Instead it doesn't seem to work. I tried replacing "this" with "document.body" in the function so the problem should be caused by "this".
Here is the code:
xmlDom=xmlhttp.responseXML;
hint_ul=document.getElementById("hint_ul");
personaggi=xmlDom.documentElement.getElementsByTagName("personaggio");
for(i=0;i<personaggi.length;i++){
personaggio=personaggi.item(i);
name=personaggio.childNodes[1].firstChild.nodeValue;
if(name.substr(0, str.length).toLowerCase()==str.toLowerCase()){
document.body.crea_li=create_tag(name);
}
}
}
function create_tag(inner){
a=document.createElement("a");
a.innerHTML=inner;
this.appendChild(a); }
this will be window when called like that.
To get its this as the body element, call it like so...
document.body.crea_li = create_tag.call(document.body, name);
Nowhere in your code is create_tag assigned as a method of document.body. The closest you get is with the line document.body.crea_li=create_tag(name);, but what's actually happening here is that you are executing create_tag as a member of the global object, and the result of that operation is assigned to document.body.crea_li.
You could make a reference to this outside the function body - referencing it within the scope later:
var self = this;
function create_tag(inner){
a=document.createElement("a");
a.innerHTML=inner;
self.appendChild(a);
}
This could be a nice trick. When I make complicated javascript objects involving many objects and functions, at the top of the object I create:
var self = this;
as that will live within the scope, the root object is always accessible.
Here is a working example of how I would implement this:
SomeReallyComplexThing = function() {
var self = this;
var foo = 'bar'
this.fooThing = 'Other thing'
this.setSomeData = function(){
console.log('Some data set', arguments)
}
this.makeMassiveCall = function() {
var completeFunc = function(){};
var url = '/some/endpoint.json';
var requestData = {};
jQuery.get(url, requestData, function(data) {
/*
* Data has come back
*/
self.setSomeData(data)
completeFunc(data);
});
}
}
//outside the scope
s = new SomeReallyComplexThing()
s.fooThing() //visible
s.self //undefined
this in javascript is a sqirrely fellow. The idea is this refers to the current function context.
This means that when your running code inside the function this refers to that function's context, which does not have an appendChild method.
Normally you use a closure to keep a reference to the calling context around, something like this
var _self = this;
var result = func();
function func()
{
// _self is the calling context, this is the current context
}
Or you could pass a reference to the calling context:
document.body.crea_li=create_tag(name,this);
function create_tag(inner, context) { context.body.appendChild(...) }
this is referring to the function's parent, but its parent is actually the window object, not the document object or document.body. this actually refers to wherever context the function is called from, and in my opinion you should avoid using it to call methods just for that reason because it can be difficult to see what this is actually referring to. For example, if you called a function using this from another function, it would refer to the context within that function.
This example might help show what's going on:
var hello = function() {
alert( this.message );
}
window.message = "hello!";
hello()
You could document.body directly in the code like you suggested before, or you could pass another parameter that tells the function where to append the created tag:
function create_tag(inner, elementToAddTag){
a=document.createElement("a");
a.innerHTML=inner;
elementToAddTagTo.appendChild(a);
}

Is it possible to restrict the scope of a javascript function?

Suppose I have a variables in the global scope.
Suppose I wish to define a function which I can guarantee will not have access to this variable, is there a way to wrap the function, or call the function, that will ensure this?
In fact, I need any prescribed function to have well defined access to variables, and that access to be defined prior to, and separate from that function definition.
Motivation:
I'm considering the possibility of user submitted functions. I should be able to trust that the function is some variety of "safe" and therefore be happy publishing them on my own site.
Run the code in an iframe hosted on a different Origin. This is the only way to guarantee that untrusted code is sandboxed and prevented from accessing globals or your page's DOM.
Using embedded Web Workers could allow to run safe functions. Something like this allows a user to enter javascript, run it and get the result without having access to your global context.
globalVariable = "I'm global";
document.getElementById('submit').onclick = function() {
createWorker();
}
function createWorker() {
// The text in the textarea is the function you want to run
var fnText = document.getElementById('fnText').value;
// You wrap the function to add a postMessage
// with the function result
var workerTemplate = "\
function userDefined(){" + fnText +
"}\
postMessage(userDefined());\
onmessage = function(e){console.log(e);\
}"
// web workers are normally js files, but using blobs
// you can create them with strings.
var blob = new Blob([workerTemplate], {
type: "text/javascript"
});
var wk = new Worker(window.URL.createObjectURL(blob));
wk.onmessage = function(e) {
// you listen for the return.
console.log('Function result:', e.data);
}
}
<div>Enter a javascript function and click submit</div>
<textarea id="fnText"></textarea>
<button id="submit">
Run the function
</button>
You can try these for example by pasting it in the textarea:
return "I'm a safe function";
You can see that it's safe:
return globalVariable;
You can even have more complex scripts, something like this:
var a = 4, b = 5;
function insideFn(){
// here c is global, but only in the worker context
c = a + b;
}
insideFn();
return c;
See info about webworkers here, especially embedded web workers:
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Embedded_workers
A little late, but maybe it will help you a bit
function RestrictFunction(params) {
params = ( params == undefined ? {} : params );
var scope = ( params.scope == undefined ? window : params.scope );
var data = ( params.data == undefined ? {} : params.data );
var script = ( params.script == undefined ? '' : params.script );
if (typeof params.script == 'function') {
script = params.script.toString();
script = script.substring(script.indexOf("{") + 1, script.lastIndexOf("}"));
}
// example: override native functions that on the white list
var setTimeout = function(_function,_interval) {
// this is important to prevent the user using `this` in the function and access the DOM
var interval = scope.setTimeout( function() {
RestrictFunction({
scope:scope,
data:data,
script:_function
});
} , _interval );
// Auto clear long user intervals
scope.setTimeout( function() {
scope.clearTimeout(interval);
} , 60*1000 );
return interval;
}
// example: create custom functions
var trace = function(str) {
scope.console.log(str);
}
return (function() {
// remove functions, objects and variables from scope
var queue = [];
var WhiteList = [
"Blob","Boolean","Date","String","Number","Object","Array","Text","Function",
"unescape","escape","encodeURI","encodeURIComponent","parseFloat","parseInt",
"isNaN","isFinite","undefined","NaN",
"JSON","Math","RegExp",
"clearTimeout","setTimeout"
];
var properties = Object.getOwnPropertyNames(scope);
for (var k = 0; k<properties.length; k++ ) {
if (WhiteList.indexOf(properties[k])!=-1) continue;
queue.push("var "+properties[k]+" = undefined;");
}
for (var k in scope) {
if (WhiteList.indexOf(k)!=-1) continue;
queue.push("var "+k+" = undefined;");
}
queue.push("var WhiteList = undefined;");
queue.push("var params = undefined;") ;
queue.push("var scope = undefined;") ;
queue.push("var data = undefined;") ;
queue.push("var k = undefined;");
queue.push("var properties = undefined;");
queue.push("var queue = undefined;");
queue.push("var script = undefined;");
queue.push(script);
try {
return eval( '(function(){'+ queue.join("\n") +'}).apply(data);' );
} catch(err) { }
}).apply(data);
}
Example of use
// dummy to test if we can access the DOM
var dummy = function() {
this.notify = function(msg) {
console.log( msg );
};
}
var result = RestrictFunction({
// Custom data to pass to the user script , Accessible via `this`
data:{
prop1: 'hello world',
prop2: ["hello","world"],
prop3: new dummy()
},
// User custom script as string or function
script:function() {
trace( this );
this.msg = "hello world";
this.prop3.notify(this.msg);
setTimeout( function() {
trace(this);
} , 10 );
trace( data );
trace( params );
trace( scope );
trace( window );
trace( XMLHttpRequest );
trace( eval );
return "done!"; // not required to return value...
},
});
console.log( "result:" , result );
You can't restrict the scope of a Function using the "call" or "apply" methods, but you can use a simple trick using "eval" and scoping to essentially hide any specific global variables from the function to be called.
The reason for this is because the function has access to the "global" variables that are declared at the scope that the function itself what declared. So, by copying the code for the method and injecting it in eval, you can essentially change the global scope of the function you are looking to call. The end result is essentially being able to somewhat sandbox a piece of javascript code.
Here's a full code example:
<html>
<head>
<title>This is the page title.</title>
<script>
function displayTitle()
{
alert(document.title);
}
function callMethod(method)
{
var code = "" +
// replace global "window" in the scope of the eval
"var window = {};" +
// replace global "document" in the scope of the eval
"var document = {}; " +
"(" +
// inject the Function you want to call into the eval
method.toString() +
// call the injected method
")();" +
"";
eval(code);
}
callMethod(displayTitle);
</script>
</head>
<body></body>
</html>
The code that gets eval'd looks like this:
var window = {};
var document = {};
(function displayTitle()
{
alert(document.title);
})();
You can use WebWorkers to isolate your code:
Create a completely separate and parallel execution environment (i.e. a separate thread or process or equivalent construct), and run the rest of these steps asynchronously in that context.
Here is a simple example:
someGlobal = 5;
//As a worker normally take another JavaScript file to execute we convert the function in an URL: http://stackoverflow.com/a/16799132/2576706
function getScriptPath(foo) {
return window.URL.createObjectURL(new Blob([foo], {
type: 'text/javascript'
}));
}
function protectCode(code) {
var worker = new Worker(getScriptPath(code));
}
protectCode('console.log(someGlobal)'); // prints 10
protectCode('console.log(this.someGlobal)');
protectCode('console.log(eval("someGlobal"))');
protectCode('console.log(window.someGlobal)');
This code will return:
Uncaught ReferenceError: someGlobal is not defined
undefined
Uncaught ReferenceError: someGlobal is not defined and
Uncaught ReferenceError: window is not defined
so you code is now safe.
EDIT: This answer does not hide the window.something variables. But it has a clean way to run user-defined code. I am trying to find a way to mask the window variables
You can use the javascript function Function.prototype.bind() to bind the user submitted function to a custom scope variable of your choosing, in this custom scope you can choose which variables to share with the user defined function, and which to hide. For the user defined functions, the code will be able to access the variables you shared using this.variableName. Here is an example to elaborate on the idea:
// A couple of global variable that we will use to test the idea
var sharedGlobal = "I am shared";
var notSharedGlobal = "But I will not be shared";
function submit() {
// Another two function scoped variables that we will also use to test
var sharedFuncScope = "I am in function scope and shared";
var notSharedFuncScope = "I am in function scope but I am not shared";
// The custom scope object, in here you can choose which variables to share with the custom function
var funcScope = {
sharedGlobal: sharedGlobal,
sharedFuncScope: sharedFuncScope
};
// Read the custom function body
var customFnText = document.getElementById("customfn").value;
// create a new function object using the Function constructor, and bind it to our custom-made scope object
var func = new Function(customFnText).bind(funcScope);
// execute the function, and print the output to the page.
document.getElementById("output").innerHTML = JSON.stringify(func());
}
// sample test function body, this will test which of the shared variables does the custom function has access to.
/*
return {
sharedGlobal : this.sharedGlobal || null,
sharedFuncScope : this.sharedFuncScope || null,
notSharedGlobal : this.notSharedGlobal || null,
notSharedFuncScope : this.notSharedFuncScope || null
};
*/
<script type="text/javascript" src="app.js"></script>
<h1>Add your custom body here</h1>
<textarea id="customfn"></textarea>
<br>
<button onclick="submit()">Submit</button>
<br>
<div id="output"></div>
The example does the following:
Accept a function body from the user
When the user clicks submit, the example creates a new function object from the custom body using the Function constructor. In the example we create a custom function with no parameters, but params can be added easily as the first input of the Function constructor
The function is executed, and its output is printed on the screen.
A sample function body is included in comments, that tests which of the variables does the custom function has access to.
Create a local variable with the same name.
If you have a global variable like this:
var globalvar;
In your function:
function noGlobal(); {
var globalvar;
}
If the function refers to globalvar, it will refers to the local one.
In my knowledge, in Javascript, any variable declared outside of a function belongs to the global scope, and is therefore accessible from anywhere in your code.
Each function has its own scope, and any variable declared within that function is only accessible from that function and any nested functions. Local scope in JavaScript is only created by functions, which is also called function scope.
Putting a function inside another function could be one possibility where you could achieve reduced scope ( ie nested scope)
if you are talking about a function that is exposed to you by loading a third party script, you are pretty much out of luck. that's because the scope for the function is defined in the source file it's defined in. sure, you can bind it to something else, but in most cases, that's going to make the function useless if it needs to call other functions or touch any data inside it's own source file - changing it's scope is only really feasible if you can predict what it needs to be able to access, and have access to that yourself - in the case of a third party script that touches data defined inside a closure, object or function that's not in your scope, you can't emulate what might need.
if you have access to the source file then it's pretty simple - look at the source file, see if it attempts to access the variable, and edit the code so it can't.
but assuming you have the function loaded, and it doesn't need to interact with anything other than "window", and for academic reasons you want to do this, here is one way - make a sandbox for it to play in. here's a simple shim wrapper that excludes certain items by name
function suspectCode() {
console.log (window.document.querySelector("#myspan").innerText);
console.log('verbotten_data:',typeof verbotten_data==='string'?verbotten_data:'<BANNED>');
console.log('secret_data:',typeof secret_data==='string'?secret_data:'<BANNED>'); // undefined === we can't
console.log('window.secret_data:',typeof window.secret_data==='string'?window.secret_data:'<BANNED>');
secret_data = 'i changed the secret data !';
console.log('secret_data:',typeof secret_data==='string'?secret_data:'<BANNED>'); // undefined === we can't
console.log('window.secret_data:',typeof window.secret_data==='string'?window.secret_data:'<BANNED>');
}
var verbotten_data = 'a special secret';
window.secret_data = 'special secret.data';
console.log("first call the function directly");
suspectCode() ;
console.log("now run it in a sandbox, which banns 'verbotten_data' and 'secret_data'");
runFunctionInSandbox (suspectCode,[
'verbotten_data','secret_data',
// we can't touch items tied to stack overflows' domain anyway so don't clone it
'sessionStorage','localStorage','caches',
// we don't want the suspect code to beable to run any other suspect code using this method.
'runFunctionInSandbox','runSanitizedFunctionInSandbox','executeSandboxedScript','shim',
]);
function shim(obj,forbidden) {
const valid=Object.keys(obj).filter(function(key){return forbidden.indexOf(key)<0;});
var shimmed = {};
valid.forEach(function(key){
try {
shimmed[key]=obj[key];
} catch(e){
console.log("skipping:",key);
}
});
return shimmed;
}
function fnSrc (fn){
const src = fn.toString();
return src.substring(src.indexOf('{')+1,src.lastIndexOf('}')-1);
}
function fnArgs (fn){
let src = fn.toString();
src = src.substr(src.indexOf('('));
src = src.substr(0,src.indexOf(')')-1);
src = src.substr(1,src.length-2);
return src.split(',');
}
function runSanitizedFunctionInSandbox(fn,forbidden) {
const playground = shim(window,forbidden);
playground.window = playground;
let sandboxed_code = fn.bind(playground,playground.window);
sandboxed_code();
}
function runFunctionInSandbox(fn,forbidden) {
const src = fnSrc(fn);
const args = fnArgs(fn);
executeSandboxedScript(src,args,forbidden);
}
function executeSandboxedScript(sourceCode,arg_names,forbidden) {
var script = document.createElement("script");
script.onload = script.onerror = function(){ this.remove(); };
script.src = "data:text/plain;base64," + btoa(
[
'runSanitizedFunctionInSandbox(function(',
arg_names,
['window'].concat(forbidden),
'){ ',
sourceCode,
'},'+JSON.stringify(forbidden)+')'
].join('\n')
);
document.body.appendChild(script);
}
<span id="myspan">Page Access IS OK<span>
or a slightly more involved version that allows arguments to be passed to the function
function suspectCode(argument1,argument2) {
console.log (window.document.querySelector("#myspan").innerText);
console.log(argument1,argument2);
console.log('verbotten_data:',typeof verbotten_data==='string'?verbotten_data:'<BANNED>');
console.log('secret_data:',typeof secret_data==='string'?secret_data:'<BANNED>'); // undefined === we can't
console.log('window.secret_data:',typeof window.secret_data==='string'?window.secret_data:'<BANNED>');
secret_data = 'i changed the secret data !';
console.log('secret_data:',typeof secret_data==='string'?secret_data:'<BANNED>'); // undefined === we can't
console.log('window.secret_data:',typeof window.secret_data==='string'?window.secret_data:'<BANNED>');
}
var verbotten_data = 'a special secret';
window.secret_data = 'special secret.data';
console.log("first call the function directly");
suspectCode('hello','world') ;
console.log("now run it in a sandbox, which banns 'verbotten_data' and 'secret_data'");
runFunctionInSandbox (suspectCode,['hello','sandboxed-world'],
[
'verbotten_data','secret_data',
// we can't touch items tied to stack overflows' domain anyway so don't clone it
'sessionStorage','localStorage','caches',
// we don't want the suspect code to beable to run any other suspect code using this method.
'runFunctionInSandbox','runSanitizedFunctionInSandbox','executeSandboxedScript','shim',
]);
function shim(obj,forbidden) {
const valid=Object.keys(obj).filter(function(key){return forbidden.indexOf(key)<0;});
var shimmed = {};
valid.forEach(function(key){
try {
shimmed[key]=obj[key];
} catch(e){
console.log("skipping:",key);
}
});
return shimmed;
}
function fnSrc (fn){
const src = fn.toString();
return src.substring(src.indexOf('{')+1,src.lastIndexOf('}')-1);
}
function fnArgs (fn){
let src = fn.toString();
src = src.substr(src.indexOf('('));
src = src.substr(0,src.indexOf(')'));
src = src.substr(1,src.length);
return src.split(',');
}
function runSanitizedFunctionInSandbox(fn,args,forbidden) {
const playground = shim(window,forbidden);
playground.window = playground;
let sandboxed_code = fn.bind(playground,playground.window);
sandboxed_code.apply(this,new Array(forbidden.length).concat(args));
}
function runFunctionInSandbox(fn,args,forbidden) {
const src = fnSrc(fn);
const arg_names = fnArgs(fn);
executeSandboxedScript(src,args,arg_names,forbidden);
}
function executeSandboxedScript(sourceCode,args,arg_names,forbidden) {
var script = document.createElement("script");
script.onload = script.onerror = function(){ this.remove(); };
let id = "exec"+Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString();
window.execArgs=window.execArgs||{};
window.execArgs[id]=args;
let script_src = [
'runSanitizedFunctionInSandbox(function(',
['window'].concat(forbidden),
(arg_names.length===0?'':','+arg_names.join(","))+'){',
sourceCode,
'},',
'window.execArgs["'+id+'"],',
JSON.stringify(forbidden)+');',
'delete window.execArgs["'+id+'"];'
].join('\n');
let script_b64 = btoa(script_src);
script.src = "data:text/plain;base64," +script_b64;
document.body.appendChild(script);
}
<span id="myspan">hello computer...</span>
I verified #josh3736's answer but he didn't leave an example
Here's one to verify it works
parent.html
<h1>parent</h1>
<script>
abc = 'parent';
function foo() {
console.log('parent foo: abc = ', abc);
}
</script>
<iframe></iframe>
<script>
const iframe = document.querySelector('iframe');
iframe.addEventListener('load', function() {
console.log('-calling from parent-');
iframe.contentWindow.foo();
});
iframe.src = 'child.html';
</script>
child.html
<h1>
child
</h1>
<script>
abc = 'child';
function foo() {
console.log('child foo: abc = ', abc);
}
console.log('-calling from child-');
parent.foo();
</script>
When run it prints
-calling from child-
parent foo: abc = parent
-calling from parent-
child foo: abc = child
Both child and parent have a variable abc and a function foo.
When the child calls into the parent's foo that function in the parent sees the parent's global variables and when the parent calls the child's foo that function sees the child's global variables.
This also works for eval.
parent.html
<h1>parent</h1>
<iframe></iframe>
<script>
const iframe = document.querySelector('iframe');
iframe.addEventListener('load', function() {
console.log('-call from parent-');
const fn = iframe.contentWindow.makeFn(`(
function() {
return abc;
}
)`);
console.log('from fn:', fn());
});
iframe.src = 'child.html';
</script>
child.html
<h1>
child
</h1>
<script>
abc = 'child';
function makeFn(s) {
return eval(s);
}
</script>
When run it prints
-call from parent-
from fn: child
showing that it saw the child's abc variable not the parent's
note: if you create iframes programmatically they seem to have to be added to the DOM or else they won't load. So for example
function loadIFrame(src) {
return new Promise((resolve) => {
const iframe = document.createElement('iframe');
iframe.addEventListener('load', resolve);
iframe.src = src;
iframe.style.display = 'none';
document.body.appendChild(iframe); // iframes don't load if not in the document?!?!
});
}
Of course in the child above we saw that the child can reach into the parent so this code is NOT SANDBOXED. You'd probably have to add some stuff to hide the various ways to access the parent if you want make sure the child can't get back but at least as a start you can apparently use this technique to give code a different global scope.
Also note that of course the iframes must be in the same domain as the parent.
Here's another answer. This one's based on how chained scopes work in javascript. It also uses Function(), which produces faster code than eval.
/** This takes a string 'expr', e.g. 'Math.max(x,1)' and returns a function (x,y)=>Math.max(x,1).
* It protects against malicious input strings by making it so that, for the function,
* (1) no names are in scope other than the parameters 'x' and 'y' and a whitelist
* of other names like 'Math' and 'Array'; (2) also 'this' binds to the empty object {}.
* I don't think there's any way for malicious strings to have any effect.
* Warning: if you add something into the global scope after calling make_fn but
* before executing the function you get back, it won't protect that new thing.
*/
function make_fn(expr) {
const whitelist = ['Math', 'Array']; // Warning: not 'Function'
let scope = {};
for (let obj = this; obj; obj = Object.getPrototypeOf(obj)) {
Object.getOwnPropertyNames(obj).forEach(name => scope[name] = undefined);
}
whitelist.forEach(name => scope[name] = this[name]);
const fn = Function("scope","x","y","with (scope) return "+expr).bind({});
return (x,y) => fn(scope,x,y);
}
This is how it behaves: https://jsfiddle.net/rkq5otme/
make_fn("x+y")(3,5) ==> 8
make_fn("Math.max(x,y)")(3,5) ==> 5
make_fn("this")(3,5) ==> {}
make_fn("alert('oops')") ==> TypeError: alert is not a function
make_fn("trace((function(){return this}()))")(3,5) ==> ReferenceError: trace is not defined
Explanation. Consider a simpler version
Function("x", "return Math.max(x, window, this);")
This creates a function with the specified body which has two chained scopes: (1) the function scope which binds x, (2) the global scope. Let's spell out how the symbols Math, x, window and this are all resolved.
x is bound to the property in the function scope
window is bound to the property in the next chained scope, global, i.e. window['window'].
We don't want this! To prevent it, we create our own scope object scope = {window:undefined,...} and write with (scope) return Math.max(x,window,this). The with statement is only allowed in non-strict code. It adds an additional scope, so the chain is now: (0) the specified scope object we created, (1) the function scope which binds x, (2) the global scope. Because name 'window' is found in our scope object, it can never bind to the one in global scope.
Math will suffer the same fate.
We want to whitelist it! So we make our scope object {Math:global.Math, window:undefined, ...}
this is bound upon invocation of the function to the global object, window.
We don't want this! To prevent it, we call .bind({}) on the function, which wraps the function in a wrapper which sets this={}. Alas bind(null) didn't seem to bind.
Note: there's another possible construction of the scope object, using Proxy. It doesn't seem particularly better.
const scope = new Proxy({}, {
has: (obj, key) => !['x','y'].includes(key),
get: (obj, key) => (whitelist.includes(key)) ? this[key] : undefined,
set: (obj, key, value) => {},
deleteProperty: (obj, key) => {},
enumerate: (obj, key) => [],
ownKeys: (obj, key) => [],
defineProperty: (obj, key, desc) => {},
getOwnPropertyDescriptor: (obj, key) => undefined,
});

Categories