Why won't window.btoa work on – ” characters in Javascript?

Viewed 1308

So I'm converting a string to BASE64 as shown in the code below...

var str = "Hello World";
var enc = window.btoa(str);

This yields SGVsbG8gV29ybGQ=. However if I add these characters – ” such as the code shown below, the conversion doesn't happen. What is the reason behind this? Thank you so much.

var str = "Hello – World”";
var enc = window.btoa(str);
4 Answers

btoa is an exotic function in that it requires a "Binary String", which is an 8-bit clean string format. It doesn't work with unicode values above charcode 255, such as used by your em dash and "fancy" quote symbol.

You'll either have to turn your string into a new string that conforms to single byte packing (and then manually reconstitute the result of the associated atob), or you can uri encode the data first, making it safe:

> var str = `Hello – World`;
> window.btoa(encodeURIComponent(str));
"SGVsbG8lMjAlRTIlODAlOTMlMjBXb3JsZA=="

And then remember to decode it again when unpacking:

> var base64= "SGVsbG8lMjAlRTIlODAlOTMlMjBXb3JsZA==";
> decodeURIComponent(window.atob(base64));
"Hello – World"

The most bullet proof way is to work on binary data directly.

For this, you can encode your string to an ArrayBuffer representing the UTF-8 version of your string.

Then a FileReader will be able to give you the base64 quite easily.

var str = "Hello – World”";
var buf = new TextEncoder().encode( str );
var reader = new FileReader();
reader.onload = evt => { console.log( reader.result.split(',')[1] ); };
reader.readAsDataURL( new Blob([buf]) );

And since Blob constructor does automagically encode DOMStrings to UTF-8, we could even get rid of the TextEncoder:

var str = "Hello – World”";
var reader = new FileReader();
reader.onload = evt => { console.log( reader.result.split(',')[1] ); };
reader.readAsDataURL( new Blob([str]) );

The Problem is the character lies outside of Latin1 range.

For this you can use unescape (now deprecated)

var str = "Hello – World”";
var enc = btoa(unescape(encodeURIComponent(str)));

alert(enc);

And to decode:

var encStr = "SGVsbG8g4oCTIFdvcmxk4oCd";
var dec = decodeURIComponent(escape(window.atob(encStr)))

alert(dec);

This ultimately owes to a deficiency in the JavaScript type system.

JavaScript strings are strings of 16-bit code units, which are customarily interpreted as UTF-16. The Base64 encoding is a method of transforming an 8-bit byte stream into a string of digits, by taking each three bytes and mapping them into four digits, each covering 6 bits: 3 × 8 = 4 × 6. As we see, this is crucially dependent on the bit width of each symbol.

At the time the btoa function was defined, JavaScript had no type for 8-bit byte streams, so the API was defined to take the ordinary 16-bit string type as input, with the restriction that each code unit was supposed to be confined to the range [U+0000, U+00FF]; when encoded into ISO-8859-1, such a string would reproduce the intended byte stream exactly.

The character is U+2013, while is U+201D; neither of those characters fits into the above-mentioned range, so the function rejects it.

If you want to convert Unicode text into Base64, you need to pick a character encoding and convert it into a byte string first, and encode that. Asking for a Base64 encoding of a Unicode string itself is meaningless.

Related