Looked around SO and didn't find anything that seemed to match what I am trying to do..
I am trying to reference an object by a string representation, though everywhere I look I see that using eval() is bad - though can't find a way to do this without using eval()
So my use case:
I have a data attribute on a button;
data-original-data-object="window.app.myData.originalData"
When the button is clicked I need to access the actual object held at window.app.myData.originalData
Now, I know I can do:
var dataObj = eval($(this).data('original-data-object'));
Though is there any other way to do this?
If it helps, the data that is stored at window.app.myData.originalData is a JSON object.
Like this:
var obj = (function(str){
var arr = str.split('.');
if (arr[0] === 'window'){
arr.shift();
}
return arr.reduce(function(a, b){
return a[b];
}, window);
}("window.app.myData.originalData"))
A couple of solutions come to mind. The first solution is hinted at in #CD..'s answer. The second is to restrict that string via a regex to just property names so you can safely use eval.
Traversing the window object to get the value (no eval)
function getValue(s) {
var keys = s.split("."), o = window, key, i, length, undef;
if (keys[0] === "window") {
keys.shift();
}
for (i = 0, length = keys.length; i < length; i++) {
key = keys[i];
if (!(key in o) || o[key] === null || o[key] === undef) {
throw new Error("Could not get value of " + s);
}
o = o[key];
}
return o;
}
Restricting the string to valid property names:
function getValue(s) {
var regex = /^[\w$][\w.]+$/, value;
if (regex.test(s)) {
try {
value = eval(s);
}
catch (error) {
throw new Error("Could not get value of " + s + " (" + error.message + ")");
}
}
else {
throw new Error("Could not get value of " + s);
}
return value;
}
To use:
var x = getValue(this.getAttribute("data-original-data-object"));
You want to avoid using eval because it can arbitrarily execute JavaScript that you may or may not have control of. In this particular case, you know the exact kind of string you want. In my opinion, I'd use a regular expression to make sure the string just contains property names separated by dots. Security speaking, there is no difference between these two lines of code:
var x = eval("window.foo");
var x = window.foo;
Provided that you can ensure that the attribute cannot be modified in anyway that can cause harm to the site/project that this is being implemented on, I don't see any problems.
I'm not sure if this will work for your situation, but a simple solution that avoids eval may be to add "window.app.myData.originalData" with its JSON data as the property of an object that will remain in scope.
Something like:
var exampleData = { id:1, content:"..." };
var dataStore = { "window.app.myData.originalData": exampleData };
Then, in your click handler:
var retrievedData = dataStore[$(this).data('original-data-object')]; // uses "window.app.myData.originalData" to retrieve exampleData
In this case, you will need to access the data using bracket notation because of the . character in the property name. This approach should be faster and safer than trying to use eval, however.
Related
I have a JS object I would like to save in Local Storage for future use, and I cannot parse it to a string.
Code:
JSON.stringify({
a: 5,
b: function (param) {
return param;
}
})
Result:
"{"a":5}"
How do I save it for future use, if not with JSON?
(And creating my own Lexer-Parser to interupt string function I dont think is an option)
I'd recommend this approach:
Store arguments and the body in your json:
{"function":{"arguments":"a,b,c","body":"return a*b+c;"}}
Now parse json and instantiate the function:
var f = new Function(function.arguments, function.body);
I think it's save
Usually a question like this indicates an X/Y problem: You need to do X, you think Y will help you do that, so you try to do Y, can't, and ask how to do Y. It would frequently be more useful to ask how to do X instead.
But answering the question asked: You could use replacer and reviver functions to convert the function to a string (during stringify) and back into a function (during parse) to store a string version of the function, but there are all sorts of issues with doing that, not least that the scope in which the function is defined may well matter to the function. (It doesn't matter to the function you've shown in the question, but I assume that's not really representative.) And converting a string from local storage into code you may run means that you are trusting that the local storage content hasn't been corrupted in a malicious way. Granted it's not likely unless the page is already vulnerable to XSS attacks, but it's an issue to keep in mind.
Here's an example, but I don't recommend it unless other options have been exhausted, not least because it uses eval, which (like its close cousin new Function)) can be a vector for malicious code:
// The object
var obj = {
a: 5,
b: function (param) {
return param;
}
};
// Convert to JSON using a replacer function to output
// the string version of a function with /Function(
// in front and )/ at the end.
var json = JSON.stringify(obj, function(key, value) {
if (typeof value === "function") {
return "/Function(" + value.toString() + ")/";
}
return value;
});
// Convert to an object using a reviver function that
// recognizes the /Function(...)/ value and converts it
// into a function via -shudder- `eval`.
var obj2 = JSON.parse(json, function(key, value) {
if (typeof value === "string" &&
value.startsWith("/Function(") &&
value.endsWith(")/")) {
value = value.substring(10, value.length - 2);
return (0, eval)("(" + value + ")");
}
return value;
});
document.body.innerHTML = obj2.b(42);
The construct (0, eval)("(" + value + ")"); ensures that eval runs at global scope rather than within the scope of the reviver function. Normally eval has a magic ability to use the scope you call it in, but that only works when you call it directly. Indirect eval as shown (or just var e = eval; e("(" + value + ")");) doesn't have that magic ability, it runs at global scope.
You can't store functions in JSON.
The value in JSON may contain only string, number, object, array, true, false or null:
Check out it on JSON site.
One simple way of doing this is
var dstr = JSON.stringify( { a: 5
, b: x => x
}
, (k,v) => typeof v === "function" ? "" + v : v
);
I've taken to storing the function name, along with the parameter values, in an array, with the first item in the array being the function name prepended with a $, to separate them from normal arrays.
{
"object": {
"your-function": ["$functionName", "param-1", "param-2"],
"color": ["$getColor", "brand", "brand-2"],
"normal-array": ["normal", "array"]
...
}
}
In the above example I have Sass and JS functions to retrieve color values from a global map/object. Parsing the function in this manner naturally requires custom code, but in terms of "storing" functions in JSON, I like this way of doing it.
I have created JSON.parseIt() and JSON.stringifyIt() functions based on the first answer without using eval
JSON.stringifyIt = (obj)=>{
return(
JSON.stringify(obj, function(key, value) {
if (typeof value === "function") {
return "/Function(" + value.toString() + ")/";
}
if(typeof value === "string"){
return "/String(" + value.toString() + ")/"
}
return value;
})
)
}
JSON.parseIt=(json)=>{
return(
JSON.parse(json, function(key, value) {
if (typeof value === "string" &&
value.startsWith("/Function(") &&
value.endsWith(")/")) {
value = value.substring(10, value.length - 2);
var string = value.slice(value.indexOf("(") + 1, value.indexOf(")"));
if(/\S+/g.test(string)){
return (new Function(string,value.slice(value.indexOf("{") + 1, value.lastIndexOf("}"))))
}else{
return (new Function(value.slice(value.indexOf("{") + 1, value.lastIndexOf("}"))));
}
}
if (typeof value === "string" &&
value.startsWith("/String(") &&
value.endsWith(")/")){
value = value.substring(8, value.length - 2);
}
return value;
})
)
}
// DEMO
var obj = {
string:"a string",
number:10,
func:()=>{
console.log("this is a string from a parsed json function");
},
secFunc:(none,ntwo)=>{console.log(none + ntwo)} ,
confuse:"/Function(hello)/"
}
const stringifiedObj = JSON.stringifyIt(obj);
console.log("the stringified object is: ",stringifiedObj);
const parsedObj = JSON.parseIt(stringifiedObj);
// console.log("the parsed object is: ",parsedObj);
console.log(parsedObj.string);
console.log(parsedObj.number);
console.log(parsedObj.confuse);
parsedObj.func();
parsedObj.secFunc(5,6);
The problems I fixed were
Removed eval.
there was a problem in the stringifying and parsing that if I give a string like
"/Function(hello)/" will be a function when parsed
Made it to two functions
Added parameter insertation
For someone that still need include, for whatever reason, the function definition in JSON, this code can help (but can be slow depending object size):
function Object2JsonWithFunctions(o, space = null) {
var functionList = {}
var fnSeq = 0;
var snrepl = function(k,v){
if(typeof v === 'function'){
fnSeq++;
var funcName = `___fun${fnSeq}___`;
var funcText = ''+v;
functionList[funcName] = funcText
return funcName;
}
return v;
}
var RawJson = JSON.stringify(o, snrepl, space);
for(func in functionList){
var PropValue = `"${func}"`;
RawJson = RawJson.replace(PropValue, functionList[func])
}
return RawJson;}
The code will do the normal convert to JSON.
For functions, the original stringify will return as "prop":"function()..." (function as a string)... The code above will create a placeholder (e.g: "prop":"fn1") and create a function list... After, will replace every placeholder to original function body...
I have a JS object I would like to save in Local Storage for future use, and I cannot parse it to a string.
Code:
JSON.stringify({
a: 5,
b: function (param) {
return param;
}
})
Result:
"{"a":5}"
How do I save it for future use, if not with JSON?
(And creating my own Lexer-Parser to interupt string function I dont think is an option)
I'd recommend this approach:
Store arguments and the body in your json:
{"function":{"arguments":"a,b,c","body":"return a*b+c;"}}
Now parse json and instantiate the function:
var f = new Function(function.arguments, function.body);
I think it's save
Usually a question like this indicates an X/Y problem: You need to do X, you think Y will help you do that, so you try to do Y, can't, and ask how to do Y. It would frequently be more useful to ask how to do X instead.
But answering the question asked: You could use replacer and reviver functions to convert the function to a string (during stringify) and back into a function (during parse) to store a string version of the function, but there are all sorts of issues with doing that, not least that the scope in which the function is defined may well matter to the function. (It doesn't matter to the function you've shown in the question, but I assume that's not really representative.) And converting a string from local storage into code you may run means that you are trusting that the local storage content hasn't been corrupted in a malicious way. Granted it's not likely unless the page is already vulnerable to XSS attacks, but it's an issue to keep in mind.
Here's an example, but I don't recommend it unless other options have been exhausted, not least because it uses eval, which (like its close cousin new Function)) can be a vector for malicious code:
// The object
var obj = {
a: 5,
b: function (param) {
return param;
}
};
// Convert to JSON using a replacer function to output
// the string version of a function with /Function(
// in front and )/ at the end.
var json = JSON.stringify(obj, function(key, value) {
if (typeof value === "function") {
return "/Function(" + value.toString() + ")/";
}
return value;
});
// Convert to an object using a reviver function that
// recognizes the /Function(...)/ value and converts it
// into a function via -shudder- `eval`.
var obj2 = JSON.parse(json, function(key, value) {
if (typeof value === "string" &&
value.startsWith("/Function(") &&
value.endsWith(")/")) {
value = value.substring(10, value.length - 2);
return (0, eval)("(" + value + ")");
}
return value;
});
document.body.innerHTML = obj2.b(42);
The construct (0, eval)("(" + value + ")"); ensures that eval runs at global scope rather than within the scope of the reviver function. Normally eval has a magic ability to use the scope you call it in, but that only works when you call it directly. Indirect eval as shown (or just var e = eval; e("(" + value + ")");) doesn't have that magic ability, it runs at global scope.
You can't store functions in JSON.
The value in JSON may contain only string, number, object, array, true, false or null:
Check out it on JSON site.
One simple way of doing this is
var dstr = JSON.stringify( { a: 5
, b: x => x
}
, (k,v) => typeof v === "function" ? "" + v : v
);
I've taken to storing the function name, along with the parameter values, in an array, with the first item in the array being the function name prepended with a $, to separate them from normal arrays.
{
"object": {
"your-function": ["$functionName", "param-1", "param-2"],
"color": ["$getColor", "brand", "brand-2"],
"normal-array": ["normal", "array"]
...
}
}
In the above example I have Sass and JS functions to retrieve color values from a global map/object. Parsing the function in this manner naturally requires custom code, but in terms of "storing" functions in JSON, I like this way of doing it.
I have created JSON.parseIt() and JSON.stringifyIt() functions based on the first answer without using eval
JSON.stringifyIt = (obj)=>{
return(
JSON.stringify(obj, function(key, value) {
if (typeof value === "function") {
return "/Function(" + value.toString() + ")/";
}
if(typeof value === "string"){
return "/String(" + value.toString() + ")/"
}
return value;
})
)
}
JSON.parseIt=(json)=>{
return(
JSON.parse(json, function(key, value) {
if (typeof value === "string" &&
value.startsWith("/Function(") &&
value.endsWith(")/")) {
value = value.substring(10, value.length - 2);
var string = value.slice(value.indexOf("(") + 1, value.indexOf(")"));
if(/\S+/g.test(string)){
return (new Function(string,value.slice(value.indexOf("{") + 1, value.lastIndexOf("}"))))
}else{
return (new Function(value.slice(value.indexOf("{") + 1, value.lastIndexOf("}"))));
}
}
if (typeof value === "string" &&
value.startsWith("/String(") &&
value.endsWith(")/")){
value = value.substring(8, value.length - 2);
}
return value;
})
)
}
// DEMO
var obj = {
string:"a string",
number:10,
func:()=>{
console.log("this is a string from a parsed json function");
},
secFunc:(none,ntwo)=>{console.log(none + ntwo)} ,
confuse:"/Function(hello)/"
}
const stringifiedObj = JSON.stringifyIt(obj);
console.log("the stringified object is: ",stringifiedObj);
const parsedObj = JSON.parseIt(stringifiedObj);
// console.log("the parsed object is: ",parsedObj);
console.log(parsedObj.string);
console.log(parsedObj.number);
console.log(parsedObj.confuse);
parsedObj.func();
parsedObj.secFunc(5,6);
The problems I fixed were
Removed eval.
there was a problem in the stringifying and parsing that if I give a string like
"/Function(hello)/" will be a function when parsed
Made it to two functions
Added parameter insertation
For someone that still need include, for whatever reason, the function definition in JSON, this code can help (but can be slow depending object size):
function Object2JsonWithFunctions(o, space = null) {
var functionList = {}
var fnSeq = 0;
var snrepl = function(k,v){
if(typeof v === 'function'){
fnSeq++;
var funcName = `___fun${fnSeq}___`;
var funcText = ''+v;
functionList[funcName] = funcText
return funcName;
}
return v;
}
var RawJson = JSON.stringify(o, snrepl, space);
for(func in functionList){
var PropValue = `"${func}"`;
RawJson = RawJson.replace(PropValue, functionList[func])
}
return RawJson;}
The code will do the normal convert to JSON.
For functions, the original stringify will return as "prop":"function()..." (function as a string)... The code above will create a placeholder (e.g: "prop":"fn1") and create a function list... After, will replace every placeholder to original function body...
In my web app, I would like to accept arbitrary JavaScript objects as GET parameters. So I need to parse location.search in a way similar to eval, but I want to create self-contained objects only (object literals, arrays, regexps and possibly restricted-access functions):
var search =
location.search ?
decodeURIComponent(location.search).substring(1).split('&') :
['boo=alert(1)', 'filter={a: /^t/, b: function(i){return i+1;}}']
;
var params = {};
for (var i = 0, temp; i < search.length; i++){
temp = search[i].split('=');
temp = temp[1] ? temp : [temp[0], null];
params[temp[0]] = saferEval(temp[1]);
};
console.log(params);
I came up with a version of saferEval function that blocks access to global variables, but it does not block access to built-in functions like alert():
var saferEval = function(s) {
'use strict';
var deteleGlobals =
'var ' +
Object.getOwnPropertyNames(window)
.join(',')
.replace(/(?:eval|chrome:[^,]*),/g, '') +
';'
;
try {
return eval(deteleGlobals + '(' + s + ');') || s;
} catch(e) {
return s;
};
};
See my jsFiddle - alert(1) code is executed.
Note that top.location is not accessible to jsFiddle scripts, you have to run the code locally if you want to fiddle with actual query parameters like ?filter={a: /%5Cd+/g}.
I would use JSON, but I need to have regular expressions at arbitrary places inside arrays and objects. I do not send any of these object back to the server, so using eval for this shouldn't harm the security so much...
How can I convert a string (that encodes JavaScript object) into object without giving it access to global namespace and built-in functions?
UPDATE - only useful "arbitrary" objects turned out to be regexp literals...
Per your comment that you'd be interested in seeing a solution that just solves the issue of having regex values in your JSON, then you could encode all regex values as strings in normal JSON like this:
"/this is my regex/"
Then, process the JSON normally into a javascript object and then call this function on it which will recursively walk through all objects and arrays, find all items that are strings, check them for the above format and, if found, convert those items to regular expressions. Here's the code:
function isArray(obj) {
return toString.call(obj) === "[object Array]";
}
function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}
var regexTest = /^\/(.*)\/([gimy]*)$/;
function convertRegex(item) {
if (isArray(item)) {
// iterate over array items
for (var i = 0; i < item.length; i++) {
item[i] = convertRegex(item[i]);
}
} else if (isObject(item)) {
for (var prop in item) {
if (item.hasOwnProperty(prop)) {
item[prop] = convertRegex(item[prop]);
}
}
} else if (typeof item === "string") {
var match = item.match(regexTest);
if (match) {
item = new RegExp(match[1], match[2]);
}
}
return item;
}
And a sample usage:
var result = convertRegex(testObj);
Test environment that I stepped through the execution in the debugger: http://jsfiddle.net/jfriend00/bvpAX/
Until there is better solution, I will add alert (and the like) into my list of local variables which would overshadow global/built-in functions within the eval scope:
var deteleGlobals =
'var ' +
Object.getOwnPropertyNames(window)
.join(',')
.replace(/(?:eval|chrome:[^,]*),/g, '') +
',alert,confirm,prompt,setTimeout;'
;
jsFiddle
var Items = {
FormVariables: function()
{
if (this.array === 'undefined')
{
this.array = [];
}
return this.array;
}
};
This was my attempt at it and I get an error of it being undefined. Can I even have variables within Items scope like I am attempting. If so, what does the syntax look like?
I am only asking if this can be done using the var variableName = {} syntax.
EDIT:
Accessing it
var formVars = new Array();
formVars.push('[');
for (var item in gd["FormVariables"])
{
formVars.push('"' + item + '":"' + gd["FormVariables"][item] + '"');
}
formVars.push(']');
The real goal here is to take all these items and convert it to a JSON array of key/value pairs
Yes, you can use []. [] is a shortcut for new Array, just like {} is for new Object.
this.array = [];
By the way, there are no 'compiler errors' since JavaScript is not a compiled language but an interpreted one.
Also, your checking does not make much sense. You'd probably want:
if (typeof this.array === 'undefined')
since typeof returns a string. Checking for the string 'undefined' is not the same as checking for 'real' undefined. For the string, it must have been set explicitly to those characters, which is almost never the case.
I need a prototype done in this way:
Array.prototype.getname=function(){ [...]return arrayname; }
So I can:
z=new Array;
alert(z.name);
and I should have "z" in the alert.
I'm working on Chrome and caller/callee seem to return empty.
The best you can do is to always explicitly set the array's name when you create it:
var z = [];
z.name = 'z';
You could do this by having a function makeArray that sets the name passed as an argument:
function makeArray(name) {
var arr = [];
arr.name = name;
return arr;
}
The essential problem is that the several variables can point to the same array:
var z = [],
x = z;
Then what should the name be: z or x?
The problem is that a variable (like an array) can have several names. For example:
var a = new Array();
var b = a;
a[0] = "hello";
alert(b[0]);//"hello"
What is the name of the array, a or b?
Can't be done. There is no way to access the name of the variable which is storing a reference to the object. Perhaps you should explain why you need behavior like this, so someone can suggest you an alternative way to approach the problem.
The only way to do this would be to brute-force check all properties of the global object (assuming the variable is global) until you find a property that === the array. Of course, it could be referenced by multiple variables so you would have to pick one of the names you get. This implementation gets the first variable to reference the array and will work in browsers and web worker threads:
Array.prototype.getName = function () {
var prop;
for (prop in self) {
if (Object.prototype.hasOwnProperty.call(self, prop) && self[prop] === this) {
return prop;
}
}
return ""; // no name found
};
Of course, I don't recommend this at all. Do not do this.
Further testing the object.getName().. i found this 'problem':
test1='test'
test
test2='test'
test
test1.getName()
test1
test2.getName()
test1
this happens because they have the same content and getName checks for content.
a solution could be to return an Array in that case.
But I'm still searching for a definitive solution to this brainteasing problem.
So For now the best answer is Elijah's
More generally:
Object.prototype.getName = function () {
var prop;
for (prop in self) {
if (Object.prototype.hasOwnProperty.call(self, prop) && self[prop] === this && self[prop].constructor == this.constructor) {
return prop;
}
}
return ""; // no name found
};
I wonder if there are better solutions.