If I have:
var test = {toString: function(){alert("evil code"); return "test";}};
how can I convert test to a string? without calling test.toString() and without using a typeof x == "string" check since I want to allow non strings.
Note: this is for a FF extension dealing with objects from a content page's js scope.
Sadly, #Matthew Flaschen's (currently accepted) answer does not work with the Symbol class from ES6 / ES2015:
console.log("" + Symbol("foo"));
// results in an exception:
// `Uncaught TypeError: Cannot convert a Symbol value to a string`
// (at least in Chrome as of this writing).
https://jsfiddle.net/L8adq9y4/
(I have no idea why, as Symbol has a perfectly good toString() method:)
console.log(Symbol("foo").toString());
https://jsfiddle.net/v1rqfhru/
There's a solution though: the String() function seems to be able to convert any value (at least out of the ones I tried) into a String. It will even call toString() if it exists:
console.log(String("A String"));
console.log(String(undefined));
console.log(String(null));
console.log(String({value: "An arbitrary object"}));
console.log(String({toString: function(){return "An object with a toString() method";}}));
console.log(String(function(){return "An arbitrary function"}));
https://jsfiddle.net/6uc83tsc/
So, pass anything you like into String() and you'll get a pretty good result.
JavaScript allows you to modify the properties of pretty much any object that is accessible to your script, including Object.prototype itself, meaning any object is vulnerable to "evil code" in the manner that you explained.
Only primitives are guaranteed to be safe, so the only way to ensure that "evil code" is never executed is to do something like this:
function safeToString(x) {
switch (typeof x) {
case 'object':
return 'object';
case 'function':
return 'function';
default:
return x + '';
}
}
One option is:
Object.prototype.toString.call(test)
This gives:
"[object Object]"
in the case of test. Basically, it just gives type information. However, I wonder what the exact scenario is here. How is the evil object getting loaded into the page? If they can execute arbitrary code on the page, you're basically out of luck. Among other things, it is then possible to redefine Object.prototype.toString.
Your (toString: function(){alert("evil code"); return "test";}) doesn't even get parsed here, it throws a syntax error. I think you wanted to use {} instead of ().
Normally you could use an empty string and the plus operator to perform a cast:
""+test;
""+2; // "2"
""+4.5 // "4.5"
""+[1, 2, 3] // "1,2,3"
""+{} // '[object Object]'
But here, there's no real way to convert the object safely.
You can use delete test.toString to get rid of the overridden method, after that it will fall back to the normal toString method which returns '[object Object]'. You can also convert the toString method itself into a string via test.toString.toString().
"function () { alert("evil code"); return "test"; }"
It's up to you what you exactly want to do here.
You can check only undefined cases and convert it to using String constructor
like this.
let a = [1,2,3]
String(a)
Exceptional case : String(undefined) --> "undefined"
Hope it helps
You can use lodash toString method.
_.toString(null);
// => ''
_.toString(-0);
// => '-0'
_.toString([1, 2, 3]);
// => '1,2,3'
Link to documentation
In JavaScript the function String is Janus-faced.
If called as a constructor it will create an object.
If called as a function if will coerces every value into a primitive string.
console.log(typeof new String())
console.log(typeof String())
So just use String(anything) without new.
Related
This question already has answers here:
Messing with this in prototype's method
(2 answers)
Closed 1 year ago.
I'm manipulating the prototype of Object so that I can add some extension methods.
I've found out that typeof operator always returns object in case the operand is this:
Object.prototype.logType = function () { console.log(typeof this); }
"Hello".logType()
output of code above is object instead of string. I know that in JavaScript everything is indeed an object, However I need to know the exact type of the value of this. How can I achieve that?
When you call a method on a primitive, JS automatically wraps that primitive in its associative object wrapper, so:
"Hello".logType()
becomes:
new String("Hello").logType()
hence, this inside of your logType function refers to the wrapped primitive value, giving you an object. You can call .valueOf() to grab the primitive value wrapped in the object:
Object.prototype.logType = function () { console.log(typeof this.valueOf()); }
"Hello".logType(); // string
(1).logType(); // number
true.logType(); // boolean
1n.logType(); // bigint
Symbol().logType(); // symbol
(() => {}).logType(); // function
({}).logType(); // object
Or, you can use strict mode as suggested in the comments, as that keeps this as the original primitve:
Object.prototype.logType = function () { "use strict"; console.log(typeof this); }
"Hello".logType(); // string
(1).logType(); // number
true.logType(); // boolean
1n.logType(); // bigint
Symbol().logType(); // symbol
(() => {}).logType(); // function
({}).logType(); // object
When passed as this outside of strict mode, you have the very rare case that you encounter a primitive in their wrapped object form ("boxed"), as instance of String:
You can therefore check if your method was called on a string using this instanceof String instead of typeof this === 'string'. If you want to differentiate between strings and objects that inherit from String, you could use this.constructor === String instead.
To get a "regular" string back (or number for Number, or boolean from Boolean, etc.), you can call this.valueOf()
(This means you could also write typeof this.valueOf() - but note that this may be misleading because any object could return, say, a string from its valueOf method without actually having been a string originally.)
Note: You cannot differentiate between 'abc'.yourMethod() and new String('abc').yourMethod() this way because you get an instance of String either way.
(Also interesting: Seems like for newer types like BigInt or Symbol you cannot manually create such a wrapper instance, new BigInt(1n) will fail. But (function () { return this }).call(1n) would achieve the same, although I have no idea why anybody would want that...)
All that said: The easiest way to get the exact behavior you want (this being the actual string) is by defining your function in a context that is in strict mode:
(function {
'use strict'
Object.prototype.logType = function () { console.log(typeof this); }
})()
Now it will work as intended.
this is my javascript test code:
console.log( [] == 'a' );
when running in my firefox it gives this error:
"TypeError: can't convert [] to primitive type"
what does this mean? It does not seem to apply to every browser/browser version, or in my case, even a tab.
I have two chrome tabs open, if I try the code in one it gives an error, but in the other tab it works fine - making me very confused about the inconsistency.
Here is an image showing the errors
So what am I missing here, why the inconsistency on chrome? Any help appreciated!
(EDIT 1)
I've found that the error comes when adding/changing a bunch of prototypes onto the Array object, so how can I add prototypes without causing this error?
{
// if I omit the first line, the error does not occur
Array.prototype.join = Array.prototype.concat;
console.log( [] == 'a' );
}
As you note, this is a consequence of modifying the Array prototype. Specifically because the method toString is used during an equality check between an array and a primitive value.
Generally, when you use == where one operand is an object, javascript will try to convert the object to a primitive using the steps outlined in the ECMAScript Spec. Parts 3 and 4 specifically:
If hint is "string", then let methodNames be «"toString", "valueOf"».
Else, Let methodNames be «"valueOf", "toString"».
As you can see, the toString method will be called as part of the attempt to convert the array to a primitive. The array valueOf method does not return a primitive by default, and since you've overriden the toString method, now it does not return a primitive either! Thus we move past step 5 and go on to 6 which says:
Throw a TypeError exception.
Here's a demonstration:
const oldToString = Array.prototype.toString;
Array.prototype.toString = function() {
console.log("Array toString method called!");
return "[" + this.join(", ") + "]";
}
// No type error, because we converted to a primitive (in this
// case, a string) successfully.
console.log([1, 2, 3] == "[1, 2, 3]")
Array.prototype.toString = function() {
return {};
}
// Type error, because we tried to convert to a primitive using
// toString, but we got an object back?!
console.log([1, 2, 3] == "[1, 2, 3]")
To be clear, this issue has nothing to do with browser differences or Firefox specifically but is instead a consequence of your modifications combined with the ECMAScript spec which all browsers follow.
var place = "mundo"["Hola", "Ciao"];
Why does this return undefined? Just because it is garbage?
That is perfectly valid JS, though it doesn't do what you expect.
place is initialized to the 'Ciao' property of String('mundo'). Since it doesn't exist, it is initialized to undefined.
The tricky part:
"Hola","Ciao" is using the comma operator, evaluates "Hola", evaluates "Ciao" and returns "Ciao"
[...] in this case is property access
"mundo"[] "mundo" is converted to a String object to access the property on it.
Proof:
var place = "mundo"["Hola", "toString"];
console.log(place) // function toString() { [native code] }
The array operator on a string object will either try to index into the string and return a specific character from that string (on some JS implementations) or it will try to lookup a property on that object. If the index is a number, some JS implementations (I think this is non-standard behavior) will give you that character from the string.
// returns "m" in Chrome
"mundo"[0]
// returns undefined
"mundo"[9]
But, an array index that isn't a number will try to look for that property on the string object and your particular value won't be found on the string object and thus you get undefined.
// does a property lookup and returns "function toString{[native code]}
"mundo"["toString"]
// returns undefined - no propery named foo
"mundo"["foo"]
So, since there is no property on the string that resembles anything in ["Hola", "Ciao"], you get undefined. Technically, the browser is actually looking for the "Ciao" property when you give it this and because that property doesn't exist, you get undefined.
In a weird test, you can run this code to sort of see what's going on:
var str = new String("mundo");
str["Ciao"] = "Hello";
alert(str["Hola", "Ciao"]); // alerts "Hello"
Working demo of this: http://jsfiddle.net/jfriend00/e6R8a/
This all makes me wonder what in the heck you are actually trying to do that comes up with this odd construct.
Update: I have created a ticket: http://bugs.jquery.com/ticket/12191
jQuery's $.type() function returns the [[Class]] internal property (lower-cased) of an object. E.g.:
$.type( {} ) // "object"
$.type( [] ) // "array"
$.type( function () {} ) // "function"
However, it only works for these types of objects:
Boolean Number String Function Array Date RegExp Object
specified by this section of jQuery's source code:
// Populate the class2type map
jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
class2type[ "[object " + name + "]" ] = name.toLowerCase();
});
In addition to those types of objects, the ECMAScript standard defines corresponding [[Class]] internal properties for these:
Arguments Error JSON Math
This is specified in this sentence of the ECMAScript standard (in section 8.6.2):
The value of the [[Class]] internal property of a host object may be
any String value except one of "Arguments", "Array", "Boolean",
"Date", "Error", "Function", "JSON", "Math", "Number", "Object",
"RegExp", and "String".
$.type returns "object" for those types of objects:
$.type( new Error ) // "object"
$.type( JSON ) // "object"
$.type( Math ) // "object"
(function () { $.type( arguments ); /* "object" */ }())
instead of "error", "json", "math", and "arguments", which are the actual [[Class]] values here (capitalized).
I would like to make it clear that $.type could return those correct values if it wanted to, since it uses the Object.prototype.toString.call() retrieval method, which returns "[object Error]" for Error objects, for instance.
So, why does jQuery report "object" instead of those four values? I could understand JSON and Math, since those are not instances, but singleton objects. And I could even understand arguments, since that is an automatically provided object, instead of an instance explicitly created by a JavaScript program (as in var args = new Arguments;). Buy why errors? I don't see what makes Error objects special (compared to the other native types, like Date, Array, etc.).
tl;dr
$.type( new Error ) // why does this return "object" instead of "error"?
Update: Just to clarify one thing: I know why $.type returns "object" for Error instances, as I have looked into its source code, and found the code that is responsible for this behavior. I would like to know why $.type is defined to behave in such a manner.
Its because jQuery's authors either forgot about that type or they didn't care about it.
Since
Object.prototype.toString.call( new Error );
will correctly return [object Error].
So, if you're cool to have a slightly longer comparison strings, here we go:
(function( type ) {
console.log( type(new Error) );
}( Function.prototype.call.bind( Object.prototype.toString )));
Beside that, there are other "types" which jQuery doesn't care about like JSON or any DOMElement. For instance, $.type( JSON ) also returns object while the above type methods correctly returns [object JSON].
Another example $.type( document.body ) again returns object where my simple type() methods again correctly returns [object HTMLBodyElement].
So long story short, jQuery does not abstract all [[Class]] values from all available types. There might be a reason for beeing browser compatible, but for the Error type, there is none.
Actually there are a zillion types, alone for all the DOM objects. I can imagine that the jQuery authors only wanted to support native object types and no host object types which essentially JSON and any DOMxxxElement etc. are. I'm not sure about the Error object whether its native or host tho.
OK, so I submitted a ticket here: http://bugs.jquery.com/ticket/12191. It has been classified as a bug, and it is expected to be fixed in version 1.8.1.
I reckon the Error type was considered unreliable, a lot of code throws things, that needn't be returned by the Error constructor:
function foo()
{
throw 'I\'m what a lot of people call an error';
}
try
{
foo();
}
catch(e)
{
console.log(Object.prototype.toString.call(e));//[object String]
}
The omission of Arguments is even simpler to explain: 'use strict'; rather limits what you can do with the arguments object. I think it better not to promote the usage of it too much, and so does John, I think. (sorry, never spell his last name correctly - I'm not on 1st name basis with the man, though).
Math is, by many considered a bad idea for an object, it should've been just a set of functions (according to most JS people). Anyway, when would anyone pass the 1 Math instance to the $.type method? you've just Typed the Constructor! what purpose could it possibly serve to have that repeated to you?
JSON is an object, older versions of IE don't support it, so you have to include a file (like you have to do with jQuery) to have the JSON object. It's not in any way a native type, but well written format sprouted from DC's mind. What else can JS say about it, other then the fact that it's an object?
is there a way to know if a variable passed into a function is a native object? I mean, i have a function that requires only native objects as arguments, for every other type of variable it throws an error. So:
func(Array); //works
func(String); //works
func(Date); //works
func(Object); //works
...
func([]); //Throwr error
func({}); //Throws error
I want to know if there's a way to distinguish between native objects and everything else.
You'd have to do an === (or !==) against the list of accepted values (which wouldn't be that long, from your question), being aware that that could be tricked into thinking something wasn't a native that was (just from another window).
But basically:
if (obj !== Array &&
obj !== String &&
obj !== Date &&
/* ...and so on, there are only a few of them... */
) {
throw "your error";
}
Edit Re my comment about things from other windows: Be aware that constructors from one window are not === to constructors from another window (including iframes), e.g.:
var wnd = window.open('blank.html');
alert("wnd.Array === Array? " + (wnd.Array === Array));
alerts "wnd.Array === Array? false", because the Array in wnd is not the same as the Array in your current window, even though both are built-in constructors for arrays.
As far as I know, current "best practice" way to get the type of something is
var theType = Object.prototype.toString.call(theObject);
That'll give you a string that looks like "[object Array]".
Now, keep in mind that [] is an Array instance, and {} is an Object instance.
There's a "typeof" operator in JavaScript that might help.
alert (typeof arg)
The other (a little more sophisticated) approach is to use
arg.prototype.constructor
this will give the reference to a function that was used to construct the object