Goetz' Implementation of Base64 in JavaScript

This section provides a JavaScript implementation of the Base64 encoding algorithm by Goetz Heller.

If you are trying to implement Base64 encoding in JavaScript, you can look at the source code developed by Goetz Heller, at http://hellerim.net/base64_src.php.

///
// This file implements base64 encoding and decoding.
// Encoding is done by the function base64Encode(), decoding
// by base64Decode(). The naming mimics closely the corresponding
// library functions found in PHP. However, this implementation allows
// for a more flexible use.
//
// This implementation follows RFC 3548 (http://www.faqs.org/rfcs/rfc3548.html),
// so the copyright formulated therein applies.
//
// Dr.Heller Information Management, 2005 (http://www.hellerim.de).
///

// Commented out - part 1 start
/*
try {
  if (system.utility.base64 == null) {
    throw new Error('');
  };
} catch(e) { // don't install twice
  // dependencies
  try {
    eval('core');
  } catch(e) {
    throw new Error('base64: class \'core\' not installed');
  }
*/
// Commented out - part 1 end

var base64 = function(){};

// provide for class information
base64.classID = function() {
  return 'system.utility.base64';
};

//disallow subclassing
base64.isFinal = function() {
  return true;
};

// original base64 encoding
base64.encString =
   'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
// URL and file name safe encoding
base64.encStringS =
   'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';

/// BEGIN_DOC(base64).METHOD(encode)
///
// method String base64.encode(INPUTTYPE inp [, bool uc [, bool safe]])
//
// Encode input data into a base64 character string.
//
// Function arguments:
//     INPUTTYPE inp: data to be encoded. INPUTTYPE may be String or Array.
//                    Any other INPUTTYPE results in an output value of null.
//                    If INPUTTYPE is String each character is converted into
//                    two bytes each of which is encoded separately.
//     bool uc:       Optional. If this parameter has a value of 'true' which is
//                    the default, code of each character is treated as a 16-bit
//                    entity (UniCode), i.e. as two bytes. Otherwise, the codes
//                    are truncated to one byte (8-bit character set) which
//                    may result in information loss. If INPUTTYPE is Array,
//                    the value of this parameter has no effect.
//     bool safe:     Optional. If this parameter is set to true, the standard
//                    base64 character set is replaced with a modified version where
//                    the characters '+' and '/' are replace with '-' and '_',
//                    respectively, in order to avoid problems with file system
//                    namings which otherwise could occur on some systems.
//                    By default, the value of this argument is assumed to be
//                    false.
// Return value:      The function returns a character string consisting of
//                    the base64 representation of the input. Its length is a
//                    multiple of 4. If the encoding yields less than this
//                    the string is stuffed with the '=' character. In each case,
//                    the string maybe empty but not null if no error occurred.
// Errors:            Whenever an error occurs, null is returned. Parameter values
//                    not defined above are considered errors.
// Remarks:           If the input array contains something different from a
//                    byte at some position the first 8 bits only of this entity are
//                    processed silently without returning an error, which probably
//                    results in garbage converted to base64.
//
/// END_DOC
base64.encode = function(inp, uc, safe) {
  // do some argument checking
  if (arguments.length < 1) return null;
  var readBuf = new Array();    // read buffer
  if (arguments.length >= 3 && safe != true && safe != false) return null;
  var enc = (arguments.length >= 3 && safe) ? this.encStringS : this.encString;
                                // character set used
  var b = (typeof inp == "string"); // how input is to be processed
  if (!b && (typeof inp != "object") && !(inp instanceof Array)) return null;
                                // bad input
  if (arguments.length < 2) {
    uc = true;                  // set default
  } // otherwise its value is passed from the caller
  if (uc != true && uc != false) return null;
  var n = (!b || !uc) ? 1 : 2;  // length of read buffer
  var out = '';                 // output string
  var c = 0;                    // holds character code (maybe 16 bit or 8 bit)
  var j = 1;                    // sextett counter
  var l = 0;                    // work buffer
  var s = 0;                    // holds sextett

  // convert
  for (var i = 0; i < inp.length; i++) {  // read input
    c = (b) ? inp.charCodeAt(i) : inp[i]; // fill read buffer
    for (var k = n - 1; k >= 0; k--) {
      readBuf[k] = c & 0xff;
      c >>= 8;
    }
    for (var m = 0; m < n; m++) {         // run through read buffer
      // process bytes from read buffer
      l = ((l<<8)&0xff00) | readBuf[m];   // shift remaining bits one byte to the
                                          // left and append next byte
      s = (0x3f<<(2*j)) & l;              // extract sextett from buffer
      l -=s;                              // remove those bits from buffer;
      out += enc.charAt(s>>(2*j));        // convert leftmost sextett and append
                                          // it to output
      j++;
      if (j==4) {                         // another sextett is complete
        out += enc.charAt(l&0x3f);        // convert and append it
        j = 1;
      }
    }
  }
  switch (j) {                            // handle left-over sextetts
    case 2:
      s = 0x3f & (16 * l);                // extract sextett from buffer
      out += enc.charAt(s);               // convert leftmost sextett and append
                                          // it to output
      out += '==';                        // stuff
      break;
    case 3:
      s = 0x3f & (4 * l);                 // extract sextett from buffer
      out += enc.charAt(s);               // convert leftmost sextett and append
                                          // it to output
      out += '=';                         // stuff
      break;
    default:
      break;
  }

  return out;

}

/// BEGIN_DOC(base64).METHOD(decode)
///
// method RETURNTYPE base64.decode(String inp [, enum outType [, bool safe
//    [, bool lax]]])
//
// Encode input data into a base64 character string.
//
// Function arguments:
//     String inp:   base64 encoded data string to be decoded.
//     enum outType: Optional. This parameter specifies the type of the output and
//                   determines how the input data is to be interpreted.:
//                     0  - binary data; create a byte array (default)
//                     1  - 8-bit character string, assuming 1-byte characters
//                          encoded in inp
//                     2  - 16-bit (UniCode) character string, assuming 2-byte
//                          characters encoded in inp
//                   If 2 is passed to the function, but the number of base64
//                   characters is odd, a value of null is returned.
//     bool safe:    Optional. If this parameter is set to true, the standard base64
//                   character set is replaced with a modified version where
//                   the characters '+' and '/' are replaced with '-' and '_',
//                   repectively, in order to avoid problems with file system
//                   namings which otherwise could occur on some systems.
//                   By default, the value of this argument is assumed to be
//                   false.
//     bool lax:     Optional. If set to true, the function skips all input
//                   characters which cannot be processed. This includes the
//                   character '=', too, if it is followed by at least one different
//                   character before the string ends. However, if skipping
//                   infeasible characters amounts to a number
//                   of allowed base64 characters which is not a multiple of 4,
//                   this is considered an error and null is returned.
//                   If lax is set to false (the default), null is returned
//                   whenever an infeasible character is found.
//                   The purpose of this parameter is to give support in cases
//                   where data has been base64 encoded and later on was folded by
//                   some other software, e.g. '\r\n\'s have been inserted in email.
//                   exchange.
// Return value:     The function's processing result value is stored in a string or
//                   in a byte array before it is returned, depending on the value
//                   assigned to the type parameter. In each case, the value
//                   maybe empty but not null if no error occurred.
// Errors:           Whenever an error occurs, null is returned. Parameter values
//                   not defined above are considered errors.
//
/// END_DOC

base64.decode = function(inp, outType, safe, lax) {

  // do some argument checking
  if (arguments.length < 1) return null;
  if (arguments.length < 2) outType = 0 ;// produce character array by default
  if (outType != 0 && outType != 1 && outType != 2) return null;
  if (arguments.length >= 3 && safe != true && safe != false) return null;
  var sEnc = (arguments.length >= 3 && safe) ? this.encStringS : this.encString;
                                         // select encoding character set
  if (arguments.length >= 4 && lax != true && lax != false) return null;
  var aDec = new Object();               // create an associative array for decoding
  for (var p = 0; p < sEnc.length; p++) { // populate array
    aDec[sEnc.charAt(p)] = p;
  }
  var out = (outType == 0) ? new Array() : '';
  lax = (arguments.length == 4 && lax); // ignore non-base64 characters
  var l = 0;               // work area
  var i = 0;               // index into input
  var j = 0;               // sextett counter
  var c = 0;               // input buffer
  var k = 0;               // index into work area
  var end = inp.length;    // one position past the last character to be processed
  var C = '';
  // check input
  if (lax) {
    var inpS = '';         // shadow input
    var ignore = false;    // determines wether '=' must be counted
    var cnt = 0;
    for (var p = 1; p <= inp.length; p++) {
                           // check and cleanup string before trying to decode
      c = inp.charAt(end - p);
      if (c == '=') {
        if (!ignore) {
          if (++cnt > 1) ignore = true;
        } else {
          continue;
        }
      } else if (undefined != aDec[c]) { // the character is base64, hence feasible
        if (!ignore) ignore = true;      // no more '=' allowed
        inpS = c + inpS;                 // prepend c to shadow input
      }
    }
    for (var p = 0; p <= cnt; p++) {     // at most cnt '=''s were garbage, a number
      if (p == 2) return null;           // in [inpS.length, inpS.length + cnt] must
      if ((inpS.length + cnt)%4 == 0) break;  //  be a multiple of 4
    }
    if (inpS.length%4==1) return null;   // must be 0, 2, or 3 for inpS to contain
                                         // correctly base64 encoded data
    inp = inpS;                          // inp now contains feasible characters only
    end = inp.length;
  } else {
    if (inp.length%4 > 0) return null;   // invalid length
    for (var p = 0; p < 2; p++) {        // search for trailing '=''s
      if (inp.charAt(end - 1) == '=') {
        end--;
      } else {
        break;
      }
    }
  }
  // convert
  for (i = 0; i < end; i++) {
    l <<= 6;                             // clear space for next sextett
    if (undefined == (c = aDec[inp.charAt(i)])) return null;
                                         // lax must be false at this place!
    l |= (c&0x3f);    // append it
    if (j == 0) {
      j++;
      continue;                          // work area contains incomplete byte only
    }
    if (outType == 2) {
      if (k == 1) {                      // work area contains complete double byte
        out += String.fromCharCode(l>>(2*(3-j)));
                      // convert leftmost 16 bits and append them to string
        l &= ~(0xffff<<(2*(3-j)));       // clear the 16 processed bits
      }
      k = ++k%2;
    } else {                             // work area contains complete byte
      if (outType == 0) {
        out.push(l>>(2*(3-j)));          // append byte to array
      } else {
        out += String.fromCharCode(l>>(2*(3-j)));
                      // convert leftmost 8 bits and append them to String
      }
      l &= ~(0xff<<(2*(3-j)));           // clear the 8 processed bits
    }
    j = ++j%4;                           // increment sextett counter cyclically
  }
  if (outType == 2 && k == 1) return null;  // incomplete double byte in work area

  return out;
}

// Commented out - part 2 start
/*
core.installClass(base64);

} // end catch
*/
// Commented out - part 2 start

See the next section for a testing program and test results.

Table of Contents

 About This Book

Base64 Encoding

 Base64 Encoding Algorithm

 RFC 1421 - Privacy Enhancement for Email

 RFC 1521 - MIME (Multipurpose Internet Mail Extensions)

 W3C Implementation of Base64 in Java

 Sun Implementation of Base64 in Java

 Sun Implementation of Base64 in Java - Test

Goetz' Implementation of Base64 in JavaScript

 Goetz' Implementation of Base64 in JavaScript - Test

 Base64 Encoding and Decoding Tools

 Base64URL - URL Safe Base64 Encoding

 Base32 Encoding

 URL Encoding, URI Encoding, or Percent Encoding

 UUEncode Encoding

 References

 Full Version in PDF/EPUB