Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Arg, scooped! I was working on this exact same thing! :D

Since you've beat me to it, let me offer up a couple additional tricks you might want to use. If you want to make this completely independent of browser API's, you can eliminate the dependence on window.location (or atob/btoa as the sla.ckers.org poster did).

Trick #1 is to get the letter "S".

You can extract this from the source code of the String constructor, but you want to be careful to make this as portable as possible. The ES spec doesn't mandate much about the results of Function.prototype.toString, although it "suggests" that it should be in the form of a FunctionDeclaration. In practice you can count on it starting with [whitespace] "function" [whitespace] [function name]. So how to eliminate the whitespace?

For this, we can make use of JS's broken isNaN global function, which coerces its argument to a number before doing its test. It just so happens that whitespace coerces to NaN, whereas alphabetical characters coerce to 0. So isNaN is just the predicate we need to strip out the whitespace characters. So we can reliably get the string "S" from:

[].slice.call(String+"").filter(isNaN)[8]

Of course, to get isNaN you need the Function("return isNaN")() trick, and you know how the rest of the encoding works.

Trick #2 then lets you get any lowercase letter, in particular "p".

For this, we can make use of the fact that toString on a number allows you to pick a radix other than 2, 8, 10, or 16. Again, the ES spec doesn't mandate this, but in practice it's widely implemented, and the spec does say that if you implement it its behavior needs to be the proper generalization of the other radices. So we can get things like:

(25).toString(26) // "p"

(17).toString(18) // "h"

(22).toString(23) // "m"

and other hard-to-achieve letters.

But once you've got "p", you're home free with escape and unescape, as you said in your post.

Dave



Great idea Dave, I considered using String+"" but didn't know how standard it was, so I discarded it.

The slice & isNaN trick is brilliant!


PS I flipped the logic in my explanation; whitespace coerces to 0 and letters coerce to NaN. Which is why filter removes the whitespace and not the letters.


Now that you mention it — V8 has an interesting bug with excessive Number#toString() decimal digits (http://code.google.com/p/v8/issues/detail?id=1627).

For example:

    (1.1536999999997645e-10).toString(33).match(/[a-z]+/g)[81]; // 'oops'
More here: https://gist.github.com/1153826


Funky! Luckily that bug doesn't interfere with this trick, since it's only relying on pretty-printing integers.

Dave


Thanks for this great comment & thanks OP!

Is there some reason not to use 36 as a radix and access the whole lowercase alphabet like

    (10).toString(36) // "a"
    ...
    (36).toString(36) // "z"
? I'm curious why you use varied combinations of radixes & base numbers.

EDIT: Friend pointed out that you are only extending the number set out to what's required for that one character. Makes sense now. :)


I think even though that's not guaranteed by the standard, it's a lot more portable in principle than relying on the DOM.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: