Skip to main content

Extends string prototype with the following methods: encodeUTF8, decodeUTF8, encodeBase64, decodeBase64. The extensions do not depend on any other code, or overwrite existing methods.

/**
 * Codecs extensions.
 *
 * Extends string prototype with the following methods:
 * decodeBase64, encodeBase64, decodeUTF8, encodeUTF8
 *
 * This extensions doesn't depend on any other code or overwrite existing methods.
 *
 * Copyright (c) 2007 Harald Hanek (http://js-methods.googlecode.com)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.gnu.org/licenses/gpl.html) licenses.
 *
 * @author Harald Hanek
 * @version 0.9
 * @lastchangeddate 09. October 2007 16:13:10
 * @revision 876
 */

(function() {

    /**
     * Extend the string prototype with the method under the given name if it
     * doesn't currently exist.
     *
     * @private
     */
    function append(name, method) {
        if (!String.prototype[name])
            String.prototype[name] = method;
    }

    /**
     * Decodes a Base64 encoded string to a byte string.
     *
     * @example "SmF2YVNjcmlwdA==".decode_base64();
     * @result "JavaScript"
     *
     * @name decodeBase64
     * @return String
     */
    append("decodeBase64", function() {
        if ((this.length % 4) == 0) {
            if (typeof(atob) != "undefined")
                return atob(this); //try using mozillas builtin codec
            else {
                var nBits, sDecoded = new Array(this.length / 4),
                    base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';

                for (var i = 0; i < this.length; i += 4) {
                    nBits = (base64.indexOf(this.charAt(i)) & 0xff) << 18 |
                        (base64.indexOf(this.charAt(i + 1)) & 0xff) << 12 |
                        (base64.indexOf(this.charAt(i + 2)) & 0xff) << 6 |
                        base64.indexOf(this.charAt(i + 3)) & 0xff;
                    sDecoded[i] = String.fromCharCode((nBits & 0xff0000) >> 16, (nBits & 0xff00) >> 8, nBits & 0xff);
                }
                sDecoded[sDecoded.length - 1] = sDecoded[sDecoded.length - 1].substring(0, 3 - ((this.charCodeAt(i - 2) == 61) ? 2 : (this.charCodeAt(i - 1) == 61 ? 1 : 0))); // make sure padding chars are left out.
                return sDecoded.join("");
            }
        } else
            throw new Error("String length must be divisible by 4.");
    });

    /**
     * Encodes a string using Base64.
     *
     * @example "JavaScript==".encode_base64();
     * @result "SmF2YVNjcmlwdA=="
     *
     * @name encodeBase64
     * @return String
     */
    append("encodeBase64", function() {
        if (typeof(btoa) != "undefined")
            return btoa(this); // using mozillas builtin codec
        else {
            var base64 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
                    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
                    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
                ],
                sbin, pad = 0,
                s = "" + this;

            if ((s.length % 3) == 1) {
                s += String.fromCharCode(0);
                s += String.fromCharCode(0);
                pad = 2;
            } else if ((s.length % 3) == 2) {
                s += String.fromCharCode(0);
                pad = 1;
            }

            var rslt = new Array(s.length / 3),
                ri = 0; // create a result buffer, this is much faster than having strings concatinated.

            for (var i = 0; i < s.length; i += 3) {
                sbin = ((s.charCodeAt(i) & 0xff) << 16) | ((s.charCodeAt(i + 1) & 0xff) << 8) | (s.charCodeAt(i + 2) & 0xff);
                rslt[ri] = (base64[(sbin >> 18) & 0x3f] + base64[(sbin >> 12) & 0x3f] + base64[(sbin >> 6) & 0x3f] + base64[sbin & 0x3f]);
                ri++;
            }

            if (pad > 0)
                rslt[rslt.length - 1] = rslt[rslt.length - 1].substr(0, 4 - pad) + ((pad == 2) ? "==" : (pad == 1) ? "=" : "");

            return rslt.join("");
        }
    });

    /**
     * Decodes a utf-8 encoded string back into multi-byte characters.
     *
     * @example "http://www.delacap.com/products/?very%20nice".encode_uri();
     * @result "http://www.delacap.com/products/?very nice"
     *
     * @name decodeUTF8
     * @return String
     */
    append("decodeUTF8", function() {
        var str = this;

        // 2-byte chars
        str = str.replace(/[\u00c0-\u00df][\u0080-\u00bf]/g, function(c) {
            var cc = (c.charCodeAt(0) & 0x1f) << 6 | c.charCodeAt(1) & 0x3f;
            return String.fromCharCode(cc);
        });

        // 3-byte chars
        str = str.replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, function(c) {
            var cc = (c.charCodeAt(0) & 0x0f) << 12 | (c.charCodeAt(1) & 0x3f << 6) | c.charCodeAt(2) & 0x3f;
            return String.fromCharCode(cc);
        });
        return str;
    });

    /**
     * Encodes a multi-byte string into utf-8 multiple single-byte characters.
     *
     * @example "http://www.delacap.com/products/?very%20nice".encode_uri();
     * @result "http://www.delacap.com/products/?very nice"
     *
     * @name encodeUTF8
     * @return String
     */
    append("encodeUTF8", function() {
        var str = this;

        // U+0080 - U+07FF = 2-byte chars
        str = str.replace(/[\u0080-\u07ff]/g, function(c) {
            var cc = c.charCodeAt(0);
            return String.fromCharCode(0xc0 | cc >> 6, 0x80 | cc & 0x3f);
        });

        // U+0800 - U+FFFF = 3-byte chars
        str = str.replace(/[\u0800-\uffff]/g, function(c) {
            var cc = c.charCodeAt(0);
            return String.fromCharCode(0xe0 | cc >> 12, 0x80 | cc >> 6 & 0x3F, 0x80 | cc & 0x3f);
        });

        return str;
    });

})();