I support a very old PHP web framework that uses server-side rendering. I decided to implement Vue for the rendering of some modules, so I compiled a hello world app and realized deployment wouldn't be so simple.
The framework works as a giant SPA, with each module being rendered using the html output of a body() function. The output is replaced in the client's DOM without reloading the page itself.
<script> tags are banned for security reasons and will be sanitized from the resulting html. The only way to deliver JS to the client is by using an eval_js() function.
The problem is rather simple. I need to safely load JS code several times in the same DOM. I cannot load it as-is after app compilation, because from the second time onwards the code is executed (every time a user visits a module, or performs an action) the code will attempt to re-define global variables and kill the whole client.
The solution is also rather simple, just rewrite the JS code such that every global definition is transformed into a window property. This way, even if the same piece of code gets executed several times in the same DOM, it will simply replace window properties rather than attempting to re-define variables.
In example, the following input:
function Yr(t){
const b = t.prototype.hasOwnProperty;
this._init(b);
}
var hOe = sg(uOe, fOe, dOe, !1, null, "e687eb20", null, null);
const vOe = {
name: "AmmFilters",
components: {
AmmOptionSelect: pOe
}
};
new Yr({...}).$mount("#app");
Would be rewritten into:
window.Yr = function(t){
const b = t.prototype.hasOwnProperty;
this._init(b);
}
window.hOe = sg(window.uOe, window.fOe, window.dOe, !1, null, "e687eb20", null, null);
window.vOe = {
name: "AmmFilters",
components: {
AmmOptionSelect: window.pOe
}
}
new window.Yr({...}).$mount("#app");
I initially considered to write my own parser, but then realized that ES6+ syntax is no child's play. The code I will attempt to rewrite is optimized & obfuscated which means it will have all sort of complex syntax and I must be careful not to turn scoped definitions into window properties.
Any ideas on a tool that already performs this task? The resulting JS code should have no difference from the original, as global scoped variables end up in the window object anyway.
I believe it would be a fairly useful tool for various use cases, so thought about asking before attempting to reinvent the wheel.
Related
I have a node js web app that is using handlebars. Users are asking me to let them register their own handlebars helpers.
I'm quite hesitant about letting them do it... but I'll give it a go if there is a secure way of doing it so.
var Handlebars = require("handlebars");
var fs = require("fs");
var content = fs.readFileSync("template.html", "utf8");
//This helper will be posted by the user
var userHandlebarsHelpers = "Handlebars.registerHelper('foo', function(value) { return 'Foo' + value; });"
//eval(userHandlebarsHelpers); This I do not like! Eval is evil
//Compile handlebars with user submitted Helpers
var template = Handlebars.compile(content);
var handleBarContent = template({ foo: bar });
//Save compiled template and some extra code.
Thank you in advance!
Because helpers are just Javascript code, the only way you could safely run arbitrary Javascript from the outside world on your server is if you either ran it an isolated sandbox process or you somehow sanitized the code before you ran it.
The former can be done with isolated VMs and external control over the process, but that makes it quite a pain to have helper code in some external process as you now have to develop ways to even call it and pass data back and forth.
Sanitizing Javascript to be safe from running exploits on your server is a pretty much impossible task when your API set is as large as node.js. The browser has a very tightly controlled set of things that Javascript can do to keep the underlying system safe from what browser Javascript can do. node.js has none of those safeguards. You could put code in one of these helpers to erase the entire hard drive of the server or install multiple viruses or pretty much whatever evil exploit you wanted to code. So, running arbitrary Javascript will simply not be safe.
Depending upon the exact problems that need to be solved, one can something develop a data driven approach where, instead of code, the user provides some higher level set of instructions (map this to that, substitute this with that, replace this with that, display from this set of data, etc...) that is not actually Javascript, but rather some non-executable meta data. That is much more feasible to make safe because you control all the code that acts on this meta data so you just have to make sure that the code that processes the meta data isn't capable of being tricked into doing something evil.
Following #jfriend00 input and after some serious testing I found a way to do it using nodejs vm module.
Users will input their helpers with this format:
[[HBHELPER 'customHelper' value]]
value.replace(/[0-9]/g, "");
[[/HBHELPER]]
[[HBHELPER 'modulus' index mod result block]]
if(parseInt(index) % mod === parseInt(result))
block.fn(this);
[[/HBHELPER]]
//This will throw an error when executed "Script execution timed out."
[[HBHELPER 'infiniteLoop' value]]
while(1){}
[[/HBHELPER]]
I translate that block into this and execute it:
Handlebars.registerHelper('customHelper', function(value) {
//All the code is executed inside the VM
return vm.runInNewContext('value.replace(/[0-9]/g, "");', {
value: value
}, {
timeout: 1000
});
});
Handlebars.registerHelper('modulus', function(index, mod, result, block) {
return vm.runInNewContext('if(parseInt(index) % mod === parseInt(result)) block.fn(this);', {
index: index,
mod: mod,
result: result,
block: block
}, {
timeout: 1000
});
});
Handlebars.registerHelper('infiniteLoop', function(value) {
//Error
return vm.runInNewContext('while(1){}', {
value: value
}, {
timeout: 1000
});
});
I made multiple tests so far, trying to delete files, require modules, infinite loops. Everything is going perfectly, all those operations failed.
Running the handlebar helper callback function in a VM is what made this work for me, because my main problem using VM's and running the whole code inside was adding those helpers to my global Handlebars object.
I'll update if I found a way to exploit it.
I have one js files . I load it using other javascrupt file using eval() function. I have seen eval is slow and with some other limtation. Since i need to store my JS file object in cache and use it anytime i need after apllication starts. I dont want to do eval() everytime.
Is there anyway to do it in simple way.
var evalObj;
if(evalObj) {
console.log('eval object already evaluated');
_myfunctionInJSFile_(layouts.FormatDate(startTime), threadName, level, categoryName, message);
}
else {
evalObj = eval(fs.readFileSync('./myJSFile', 'utf8'));
console.log('re evaluating object ..' );
_myfunctionInJSFile_(layouts.FormatDate(startTime), threadName, level,message);
}
myJSFile
var _sigmaAlarmHandler_ =function(args)
{
var args = Array.prototype.slice.call(arguments);
args.unshift();
console.log('Alarm : ', args);
}
Either the conditional eval is not working.
In node.js you can simple require your js-file:
var obj = require('./myJSFile');
obj.foo();
./myJSFile.js:
exports.foo = function() {
console.log('foo');
}
This file becomes a module with exported functions, that you need.
It loads once, then every require reuse already loaded module.
If it is not commonjs-compliant (i.e. using module.exports will not work), then you can run it in its own vm:
var vm = require('vm');
vm.runInNewContext(jscode,{/*globalvars*/});
where the second parameter is an object with global vars made available in the context in which the jscode is run. So if the second param is, say, {a:1,b:"foo"} then your jscode will run with the global variable a set to 1 and the global variable b set to "foo".
The jscode itself is a string that you load from a file or elsewhere.
Think of vm.runInNewContext() as "practice safe eval". Well, relatively safe, you can still do some dangerous stuff if you pass in particular vars, like process or file etc.
I used this for the declarative part of cansecurity http://github.com/deitch/cansecurity for nodejs
You can view the sample in the file lib/declarative.js
Here is the API for vm http://nodejs.org/api/vm.html
There are options to run in the same context, etc. But that is very risky.
When you actually run the code, using your example above:
_myfunctionInJSFile_(layouts.FormatDate(startTime), threadName, level,message);
you are looking to pass in 4 params: startTime, threadName, level, message and execute the function. The issue is that you cannot run the function on the current context. You need the function to be defined and run in the file. So you should have something like:
vm.runInNewContext(jscode,{startTime:layouts.FormatDate(startTime),threadName:threadName,level:level,message:message});
And then the jscode should look like
function _myfunctionInJSFile(startTime,threadName,level,message) {
// do whatever you need to do
}
// EXECUTE IT - the above vars are set by the global context provide in vm.runInNewContext
_myfunctionInJSFile(startTime,threadName,level,message);
If you prefer to define the function and have it loaded and run in this context, then just use the commonjs format.
I think i have found the answer for this.
Since my application is running in node js which uses v8 engine platform. When the application starts v8 engine caches all the code/configuration and can be used anytime.
Similarly in my code i will pre-load the JS code using eval and i will do it only once. So on next call i will return only the loaded JS code. Here i need to modify the code to load once.
But main point we have look is that in future if any body has similar requirement they can cache their JS codes using eval (thanks to v8 engine) and use it till your application is running.
I'm writing a small JavaScript framework for fun and possible implementation similar to backbone(hence the tag). I've started saving elements as object properties, as shown below. I'm not sure if I've seen this done, so I was curious if this causes any issues.
Similarly, If the module depends on other modules I list those at the top of the object in the form of....another object.
I wanted a way to list dependencies ( page elements or JavaScript modules ) and detect any issues up front. This has similar ( not same ) benefits as dependency injection.
This is a specific question on this code review post which explains a bit further on how the framework works.
/*MUserTry
**
**
**
*/
$A.modelAjax({
Name: 'MUserTry',
S: {
DynSma: SDynSma,
DynTwe: SDynTwe,
DynArc: SDynArc,
AniFlipPage: SAniFlipPage,
ClientStorage: SClientStorage
},
E: {
but: $A('#ut_but')[0]
},
J: {
box: $('#ut_box')
},
init: function () {
var pipe = {},
this_hold = this;
this.J.box.draggable();
this.E.but.addEventListener("click", function () {
pipe = $A.definePipe(this_hold.Name);
$A.ajaxMachine(pipe);
}, false);
},
pre: function (pipe) {
pipe.page.email = this.e_button.getAttribute('data-email');
pipe.proceed = true;
},
post: function (pipe) {
this.S.ClientStorage.setAll(pipe.server.smalls);
this.S.DynSma.run(pipe.server.smalls);
this.S.DynArc.run(pipe.server.arcmarks);
this.S.DynTwe.run(pipe.server.tweets);
this.S.AniFlipPage.run('ma');
},
finish: function (pipe) {
$A.log(pipe);
}
});
Ok first off let me offer the obligatory "you'll never get a better wheel by re-inventing the wheel" warning. Whatever you're trying to accomplish, you're almost certainly going to be more successful with it if you use an existing library. And even if there is good cause for you to make your own, it would still benefit you immensely to at least look at existing libraries.
But ... maybe you're just having fun with this project, and looking at other projects isn't fun so you're not doing it. Fair enough.
In any case, if you do look at Backbone, you'll find that this practice is core to the Backbone View class. Every View in Backbone has an "el" and "$el" property, which refer to the raw DOM element for the view and the jQuery-wrapped version of that element.
Backbone has no real performance issues with this because variables/properties in JS are just pointers; in other words, when you set the property of an object to an element, you aren't duplicating that element, you're just adding a reference to it (to put it another way, it's more like you're an A tag rather than a whole new document).
The one time Backbone does have a problem though (and your framework will too) is with stale references. In other words, if you just remove element X from the page, the browser will stop using memory for it (eventually, via garbage collection). But if there is an object out there which points to that element, it won't get garbage-collected, because anything with a reference isn't "garbage".
So, the main thing you have to watch out for is making sure that these objects either:
A) get deleted whenever their elements do, or
B) get rid of their references (eg. delete obj.reference) when their elements get deleted
If you don't do that, things will still probably work just fine ... until you use it on a page with lots of elements being created/deleted, at which point Firefox will start popping up "this script took way too long to run, are you really sure you want to be doing this?" messages.
What is 'this' in phonegap? I am working on an app where I have local javascript packed with the app and remote javascript pulled from a server. I am using coffeescript to generate all the javascript.
Both files are being included in the app's index header, generated through haml as such:
%script{:src=>"javascripts/file1.js"}
%script{:src=>"http://192.168.5.205:3000/assets/file2.js"}
file1.js.coffee:
#myObj1 = property: true
file2.js.coffee:
#myObj2 = property: true
myObj1 is available globally and can be referenced as just myObj1, but myObj2 is only available via document.myObj2 and can not be referenced as simply myObj2. What is happening?
Well, if you say this:
#myObj2 = property: true
and you have to reference myObj2 as document.myObj2 everywhere else, then this must be document when file2.js.coffee is executed. This sort of thing can happen if your code is executed through $(document).ready() or similar means (for example: http://jsfiddle.net/ambiguous/6DFK9/).
If you want something to be global, then you really should be explicit about it and use window:
# in file1.js.coffee:
window.myObj1 = property: true
# in file2.js.coffee:
window.myObj2 = property: true
That way you don't have to worry about the calling context and more importantly, your intent will be explicit and you won't have to puzzle over what this is or supposed to be in six months when you're trying to fix bugs. Say what you mean: if you want it global, put it in window.
So I just need a sanity check on the way in which I layout my code for an application. I'm always keen to learn better approaches.
I basically use an Object Literal to organise my code meaning that I have one single global variable. Then for each section of the application I create a separate object - something like:
var MYAPP = {
init : function() {
//site wide common js
},
sections : {
homepage : function() {
//homepage js
},
anotherpage : function() {
//another page js
tools.usefultool();
}
},
tools : {
usefultool : function() {
//useful reuseable method
}
}
};
My question is while this helps code organisation, I'm wondering about objects being initialised but never used. For example - if I'm on a site's homepage I'll just call MYAPP.sections.homepage() . I don't actually need any of the other objects so I'm wondering - does this structure have a performance implication? Is there a better way? The structure closely follows the the great Rebecca Murphy article "Using Object to Organise Your Code" (http://blog.rebeccamurphey.com/2009/10/15/using-objects-to-organize-your-code).
Thanks!
Yes, there's always a performance hit in unused code as the parser has to actually interpret the code even if it's not executed. But any performance hit here is so minute that you're never going to notice it. The only real hit in unused code like this is in the bandwidth required to download it. If you have a 100kb file downloaded that you never use then you're wasting the time to download that file.