How to convert anything to a String safely in JavaScript
Asked Answered
N

7

25

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.

Nightcap answered 30/11, 2010 at 4:28 Comment(4)
What do you expect to return without calling toString()?Thierry
Well, there's always JSON serializers...Frankel
I've fixed the example syntax.Outcry
Can anything be converted to a string? how would you expect JSON class be represented as a 'safe string'?Jehu
T
7

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 + '';
  }
}
Thierry answered 30/11, 2010 at 4:45 Comment(1)
For folks that need more granularity (e.g still wish to translate [1,2,3] to '1,2,3' CSV) using something like Lodash array/object detection could be placed in the object case: return _.isArray(x) && !_.some(x, _.isObject) ? _.toString(x) : 'object';Bellamy
F
9

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.

Freelance answered 21/10, 2016 at 21:26 Comment(2)
"Pass anything you like into String() and you'll get a pretty good result." Not quite. String([Symbol('AAAA')]) gives "Uncaught TypeError: Cannot convert a Symbol value to a string".Chink
Why does it work when not in an array, but fail in an array? That's weird...Jacquelinjacqueline
T
7

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 + '';
  }
}
Thierry answered 30/11, 2010 at 4:45 Comment(1)
For folks that need more granularity (e.g still wish to translate [1,2,3] to '1,2,3' CSV) using something like Lodash array/object detection could be placed in the object case: return _.isArray(x) && !_.some(x, _.isObject) ? _.toString(x) : 'object';Bellamy
A
6

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.

Accelerometer answered 30/11, 2010 at 4:31 Comment(3)
This will still implicitly call the "evil" toString.Outcry
@Matthew Indeed it's 5:30 am here and his example code is broken too, gonna update it.Accelerometer
The evil code could be part of a toString attached to the prototype chain, so delete test.toString() won't always work.Nightcap
O
6

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.

Outcry answered 30/11, 2010 at 4:34 Comment(6)
Even this isn't foolproof because it's possible to override Object.prototype.toString.Thierry
@casa, true. This really only helps if all they can do is pass a self-contained object into the page. If they can modify properties of globals, all bets are off.Outcry
here's the catch, the evil code is created in a different scope than my code. It's a FF ext dealing with a content page's scope. So the objects coming from that scope should have a different Object.prototype, so using Object.prototype.toString should be safe.Nightcap
I think that a combination of this answer and casablanca's switch should do the trick.Nightcap
This is the only answer that works for a const test = Object.create(null), which is not wholly uncommon.Palmirapalmistry
Object.prototype.toString.call(123) returns the string "[object Number]". Possibly not what you want to use, as the data (123) is lost...Sonatina
C
1

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

Chemash answered 22/11, 2019 at 21:41 Comment(0)
M
0

You can use lodash toString method.

_.toString(null);
// => ''

_.toString(-0);
// => '-0'

_.toString([1, 2, 3]);
// => '1,2,3'

Link to documentation

Mussolini answered 6/10, 2017 at 19:0 Comment(0)
T
0

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.

Tamqrah answered 12/2, 2023 at 14:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.