Source: convert.js

/*
 *	Data Conversion Utilities:
 *	Copyright (c) 2013, Jeff Lyon. (http://rubbingalcoholic.com)
 * 
 *	Licensed under The MIT License. (http://www.opensource.org/licenses/mit-license.php)
 *	Redistributions of files must retain the above copyright notice.
 */
/**
 *	@classdesc
 *	This static class offers a grab bag of methods for various types of data conversion operations.
 *	WARNING: unless otherwise noted, any method that accepts a string input *must* be ASCII-encoded.
 *	(ie. no UTF8 / Unicode multibyte characters values are accepted) Use {@link convert.utf8.encode}
 *	to encode any non-ASCII string data as needed. Ignore this warning at your own peril.
 *
 *	@namespace
 *	@type {Object}
 *
 *	@author		Jeff Lyon <jeff@rubbingalcoholic.com>
 *	@copyright	Copyright (c) 2013, Jeff Lyon. ({@link http://rubbingalcoholic.com})
 *	@license	{@link http://www.opensource.org/licenses/mit-license.php|The MIT License}
 */
var convert = {
	/**
	 *	Converts an ASCII string to an array of 8-bit integer bytes.
	 *
	 *	@static
	 *	@param {string} str		The input string
	 *	@return {Array}			The byte array
	 */
	to_bytes: function(str)
	{
		var bytes = [];

		for (var i = 0; i < str.length; i++)
			bytes.push(str.charCodeAt(i) & 255);
		
		return bytes;
	},

	/**
	 *	Converts an ASCII string or byte array to an array of "words" (32-bit integers)
	 *	(RA NOTE ~ Assumes the input string length is a multiple of 4)
	 *
	 *	@param {string|Array} data						The input string or array
	 *	@param {Object} [options]						Optional options object.
	 *	@param {boolean} [options.reverse_endian=false]	Whether to reverse the endian-ness (byte order) of the word bytes
	 *	@return {Array}									An array of 32-bit integers
	 */
	to_words: function(data, options)
	{
		options || (options = {});
		options.reverse_endian || (options.reverse_endian = false);

		var words		= [];
		var _to_word 	= this.to_word;

		if (typeof data != 'string')
			if (!options.reverse_endian)
				for (var i=0; i<data.length; i+=4)
					words.push(_to_word(data[i], data[i+1], data[i+2], data[i+3]));
			else
				for (var i=0; i<data.length; i+=4)
					words.push(_to_word(data[i+3], data[i+2], data[i+1], data[i]));
		else
			if (!options.reverse_endian)
				for (var i=0; i<data.length; i+=4)
					words.push(_to_word(data.charCodeAt(i), data.charCodeAt(i+1), data.charCodeAt(i+2), data.charCodeAt(i+3)));
			else
				for (var i=0; i<data.length; i+=4)
					words.push(_to_word(data.charCodeAt(i+3), data.charCodeAt(i+2), data.charCodeAt(i+1), data.charCodeAt(i)));

		return words;
	},

	/**
	 *	Joins up to 4 arbitrary 8-bit integer bytes into one 32-bit integer word
	 *
	 *	@param {number} byte1		8-bit integer. Most significant byte
	 *	@param {number} byte2		8-bit integer. Second most significant byte
	 *	@param {number} byte3		8-bit integer. Third most significant byte
	 *	@param {number} byte4		8-bit integer. Least significant byte
	 *	@return {number}			A 32 bit integer word
	 */
	to_word: function()
	{
		if (arguments.length == 4)
			return ((arguments[0] & 255) << 24) | ((arguments[1] & 255) << 16) | ((arguments[2] & 255) << 8) | (arguments[3] & 255);
		/*
		// RA NOTE ~ No longer support passing one string byte into this method. Not sure why I ever did.
		else if (typeof arguments[0] == 'string')
			return this.to_words(arguments[0]).shift();
		*/

		var joined 	= 0;	
		for (var i = arguments.length-1; i >= 0; i--)
			joined |= (arguments[i] & 255) << 8*(arguments.length-1-i);
		
		return joined; 
	},

	/**
	 *	Converts an array of 32-bit integer words to an ASCII-encoded binary string
	 *
	 *	@param {Array} words	Array of 32-bit integer words
	 *	@return {string}		ASCII-encoded binary string
	 */
	words_to_binstring: function(words)
	{
		var binary 				= '';
		var _word_to_binstring	= this.word_to_binstring;

		for (var i = 0; i < words.length; i++)
			binary += _word_to_binstring(words[i]);

		return binary;
	},

	/**
	 *	Converts an array of 32-bit integer words to a hex string
	 *
	 *	@param {Array} words	Array of 32-bit integer words
	 *	@return {string}		Hexademical string
	 */
	words_to_hex: function(words)
	{
		return this.binstring_to_hex(this.words_to_binstring(words));
	},

	/**
	 *	Converts an array of 32-bit integer words to an array of 8-bit integer bytes
	 *
	 *	@param {Array} words	Array of 32-bit integer words
	 *	@return {Array}			Array of 8-bit integer bytes
	 */
	words_to_bytes: function(words)
	{
		var bytes 				= [];
		var _word_to_bytes		= this.word_to_bytes;

		for (var i = 0; i < words.length; i++)
			bytes = bytes.concat(_word_to_bytes(words[i]));

		return bytes;
	},

	/**
	 *	Converts a 32-bit integer word to a 4 byte ASCII-encoded binary string
	 *
	 *	@param {number} word	32-bit integer word
	 *	@return {string}		ASCII-encoded binary string
	 */
	word_to_binstring: function(word)
	{
		return 		String.fromCharCode((word >>> 24) & 255)
				+ 	String.fromCharCode((word >>> 16) & 255)
				+ 	String.fromCharCode((word >>> 8) & 255)
				+	String.fromCharCode(word & 255);
	},

	/**
	 *	Splits a 32-bit integer word to an array of four 8-bit integers
	 *
	 *	@param {number} word	32-bit integer word
	 *	@param {Array}			Array of 8-bit integers
	 */
	word_to_bytes: function(word)
	{
		return [
			((word >>> 24) & 255),
			((word >>> 16) & 255),
			((word >>> 8) & 255),
			(word & 255)
		];
	},

	/**
	 *	Converts a hex string to an ASCII-encoded binary string.
	 *	
	 *	@param {string} hex		Hexadecimal string (do not prefix with '0x'}
	 *	@return {string}		ASCII-encoded binary string
	 */
	hex_to_binstring: function(hex)
	{
		if (hex.length % 2 == 1)
			hex = '0'+hex;

		var binary = '';

		for (var i = 0; i < hex.length; i += 2)
			binary += String.fromCharCode(parseInt(hex.substr(i, 2),16));

		return binary;
	},

	/**
	 *	Converts an ASCII-encoded binary string to a hexadecimal string
	 *	
	 *	@param {string} str		ASCII-encoded binary string
	 *	@return {string}		Hexadecimal string
	 */
	binstring_to_hex: function(str)
	{
		var hex = '';
		for (var i=0; i < str.length; i++)
			hex += (str.charCodeAt(i).toString(16).length == 1 ? '0' : '') + str.charCodeAt(i).toString(16);
		
		return hex;
	},

	/**
	 *	@classdesc Static class for Base64 Encoder / Decoder functionality. This is a child of {@link convert}.
	 *	@namespace
	 */
	base64:
	{
		/**
		 *	The list of characters used in the conversion process
		 *	@private
		 */
		chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',

		/**
		 *	Encodes string data into Base64 format string
		 *
		 *	@param {string} data	ASCII-encoded string data
		 *	@return {string}		Base64-encoded string
		 *
		 *	@example
		 *
		 *	convert.base64.encode("They don't call me honest nothing for abe!");
		 *	// outputs "VGhleSBkb24ndCBjYWxsIG1lIGhvbmVzdCBub3RoaW5nIGZvciBhYmUh"
		 *
		 */
		encode: function(data)
		{
			var output = '';
			for (i=0, c=data.length; i<c; i += 3)
			{
				var char1 = data.charCodeAt(i) >> 2;
				var char2 = ((data.charCodeAt(i) & 3) << 4) | data.charCodeAt(i+1) >> 4;
				var char3 = ((data.charCodeAt(i+1) & 15) << 2) | data.charCodeAt(i+2) >> 6;
				var char4 = data.charCodeAt(i+2) & 63;

				output 	+= 	this.chars.charAt(char1)
						+ 	this.chars.charAt(char2)
						+	this.chars.charAt(char3)
						+	this.chars.charAt(char4);
			}
			if (c % 3 == 1)
				output = output.substr(0, output.length - 2) + '==';
			else if (c % 3 == 2)
				output = output.substr(0, output.length - 1) + '=';
			
			return output;
		},

		/**
		 *	Decodes data from Base64 format string into plaintext
		 *
		 *	@param {string} str	Base64 string to decode
		 *	@return {string}	ASCII-encoded plaintext string
		 *
		 *	@example
		 *
		 *	convert.base64.decode("VGhleSBkb24ndCBjYWxsIG1lIGhvbmVzdCBub3RoaW5nIGZvciBhYmUh");
		 *	// outputs "They don't call me honest nothing for abe!"
		 *
		 */
		decode: function(str)
		{
			var data = '';

			for (i=0, c=str.length; i<c; i += 4)
			{
				var char1 = this.chars.indexOf(str.charAt(i));
				var char2 = this.chars.indexOf(str.charAt(i+1));
				var char3 = this.chars.indexOf(str.charAt(i+2));
				var char4 = this.chars.indexOf(str.charAt(i+3));

				data += String.fromCharCode(char1 << 2 | char2 >> 4);
				if (char3 != -1)
					data += String.fromCharCode((char2 & 15) << 4 | char3 >> 2)
				if (char4 != -1)
					data += String.fromCharCode((char3 & 3) << 6 | char4);
			}
			return data;
		}
	},

	/**
	 *	@classdesc
	 *	Static class for UTF8 Encode / Decode functionality.
	 *	This is a child of {@link convert}.
	 *
	 *	Since most of our encryption, hashing and {@link convert} methods expect
	 *	ASCII-encoded binary strings (ie. 0 <= [character code] <= 255), they
	 *	will misbehave on non-ASCII characters. We can use 
	 *  {@link convert.utf8.is_utf8_string} to test whether we need to use
	 *	{@link convert.utf8.encode} to encode to ASCII before using elsewhere.
	 *
	 *	@namespace
	 */
	utf8:
	{
		/**
		 *	Encodes UTF8 Unicode data to ASCII.
		 *
		 *	@param {string} data	A non-ASCII string
		 *	@return {string}		An ASCII string
		 *
		 *	@example
		 *
		 *	var unicode_string = "ŞĩŁĿŶ ǙƝȈʗʘɗε";
		 *	convert.utf8.encode(unicode_string);	// ASCII output: "ŞĩŁĿŶ ǙƝȈʗʘɗε"
		 *	
		 */
		encode: function(data)
		{
			return unescape(encodeURIComponent(data));
		},

		/**
		 *	Decodes encoded ASCII back to UTF-8.
		 *
		 *	@param {string} data	An ASCII string
		 *	@return {string}		A (potentially) UTF-8 String
		 *
		 *	@example
		 *
		 *	var ascii_string = "ŞĩŁĿŶ ǙƝȈʗʘɗε";		// it's really ASCII, trust me.
		 *	convert.utf8.decode(ascii_string);		// Unicode output: "ŞĩŁĿŶ ǙƝȈʗʘɗε"
		 *	
		 *
		 */
		decode: function(data)
		{
			return decodeURIComponent(escape(data));
		},

		/**
		 *	Checks to see if a string has UTF8 characters.
		 *
		 *	(We check for character codes that are multi-byte integer values > 255)
		 *
		 *	@param {string} str		A string, maybe containing UTF8 characters
		 *	@return {boolean}		True if UTF8 characters are found
		 *
		 *	@example
		 *
		 *	convert.utf8.is_utf8_string("ŞĩŁĿŶ ǙƝȈʗʘɗε");	// returns true
		 */
		is_utf8_string: function(str)
		{
			return /[^\u0000-\u00ff]/.test(str);
		}
	}
}