In this article we’ll take a look at Base64, what it is and why we use it. Although there are Base64 encoding and decoding available in the Apps Script Utilities service, we’ll write an encoder and (maybe) a decoder as a learning exercise.

What is Base64?

Base64 refers to the radix, or the number of unique characters in an encoding system. You can think of this in terms of other radices that you might be more familiar with in the table below

 Radix Name Character set number of bits one character can exactly represent (power of 2)
 2  binary  0-1 1
 8  octal  0-7 3
 10  decimal  0-9  n/a
 16  hex  0-9,a-f  4
 64  base64  0-9,A-Z,a-z,+,/  6
 256  byte  binary 0-255  8

The purpose of base64 is to represent binary data in some printable form, so it can be sent losslessly over media transports that are only designed to deal with text data. Many Http services, APIs (and email) encode their data to base64 during transmission. There are other encoding systems such as uuencode and Base85, but the most common is Base64.

As per the table above, base64 is only capable of representing 6 bits per character, but binary data could need all 8 bits in a byte. This means that a Base64 string of characters will be longer than the number of bytes in the original. In fact each group of 3 bytes needs 4 base64 characters to represent it.

Encoding base64.

It’s pretty straightforward to convert a byte array (or another string) to Base64. The only tricky bit is where the input number of characters is not exactly divisible by 3, in which case you have to use some padding.

Here’s a simple encoder. It’s written to be able to take either a string or a Byte[] as input.

/*
 * convert to base64
 * @param {string|Byte[]) input the data to be converted
 * @return {string} the encoded string
 */
function sampleB64Encode (input) {
  
  var b64Digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  
  // if input is a string, convert to a byte array
  var barry = typeof input === "string" ?  [].map.call (input,function(d) { return d.charCodeAt(0);}) : input.slice();
 
  
  // the number of groups of 3 characters will be
  var numberOfGroups = Math.floor((barry.length -1) / 3) + 1;
  
  // this is the padding character if not input length not divisible by 3
  var pad = "=";

  // pad out with 0 to make it divisible by 3
  while (barry.length  < numberOfGroups *3 ) { 
    barry.push(0); 
  }
  
  // we'll do it a group of 3 input characters at a time
  for ( var index = 0, group = 0 , output = [] ; group < numberOfGroups ; group++ ) {

    // top 6 bits of first char
    output.push ( b64Digits.charAt ( (barry [index] >> 2) & 0x3f) );
    
    // bottom 2 bits of first char, top 4 of second
    output.push ( b64Digits.charAt ( ((barry[index] & 0x03) << 4)  | ( (barry[index +1] >> 4)  & 0x0f) ) );
    
    // bottom 4 bits of 2nd char, top  2 of third
    output.push (  b64Digits.charAt ( ((barry [index +1] & 0x0f) << 2) |  ((barry[index +2] >> 6)  & 0x03) ) )  ;
    
    // bottom 6 of third
    output.push ( b64Digits.charAt (barry[index+2] & 0x3f));
      
    index += 3;
  }

  // deal with nondivisible by 3
  if ( input.length % 3 !== 0 ) {
    output [output.length-1] =  pad;
  }
  if ( input.length % 3 === 1) {
    output [output.length-2] = pad ;
  }

  // return as a string
  return output.join('');
}

Decoding Base64

It should be fairly straightforward to reverse engineer this. Your homework is to submit a decoder to me on our forum, and I’ll publish the best one.

Testing

Here’s a test comparing the results of this function to the Utilities.base64Encode(). This generates 1000 tests with random Byte Arrays and succeeds.

function test() {

  // do some tests
  for (var j=0 ; j < 1000 ; j++) {
    
    var size = Math.ceil(Math.random() * 200);
    var seed = [];
    for (var i=0;i<size;i++) {
      seed [i] = Math.ceil(Math.random() * 127) * (Math.round(Math.random()) ? -1 : 1);
    }    
    
    var conv = sampleB64Encode (seed);
    var convu = Utilities.base64Encode(seed);
    if ( conv !== convu) throw conv;

  }
}

Performance

It’s always going to be better to use the inbuilt Utilities function, but here’s a rare chance to see the difference in performance between something coded in apps script and its native counterpart. – You can see that

Utilities.base64Encode()

is over 7 times as fast as my attempt at an equivalent in apps script.

[15-04-01 14:56:02:979 BST] Time for Utilities.base64Encode(data):963
[15-04-01 14:56:02:980 BST] Time for sampleB64Encode):7494

Although, when you run this as a client app on chrome – the same code runs superfast and the difference is minimal

Time for btoa:30
main.js:34 Time for sampleB64Encode):49

Here’s the test.

function speedtest() {

  // do some tests
  function makeSeeds() {
    var seeds = [];
    
    for (var j=0 ; j < 1000 ; j++) {
      var size = Math.ceil(Math.random() * 200);
      var seed = [];
      for (var i=0;i<size;i++) {
        seed [i] = Math.ceil(Math.random() * 127) * (Math.round(Math.random()) ? -1 : 1);
      }   
      seeds.push(seed);
    }
    return seeds;
  }
  var seeds = makeSeeds();
  
  var start = new Date().getTime();
  seeds.forEach(function (d) {
    return Utilities.base64Encode(d);
  });
  var u = new Date().getTime() - start;
  
  var start = new Date().getTime();
  seeds.forEach(function (d) {
    return sampleB64Encode(d);
  });
  var s = new Date().getTime() - start;
  
  Logger.log ('Time for Utilities.base64Encode(data):' + u);
  Logger.log ('Time for sampleB64Encode):' + s);
}
Many of the snippets in this section of the site are part of the cUseful library. You can find the details here.
For more like this see Google Apps Scripts Snippets
Why not join our forum,follow the blog or follow me on twitter to ensure you get updates when they are available.