| 68 | 1 /* | 
|  | 2 Copyright (c) 2008  Franklin Schmidt <fschmidt@gmail.com> | 
|  | 3 | 
|  | 4 Permission is hereby granted, free of charge, to any person obtaining a copy | 
|  | 5 of this software and associated documentation files (the "Software"), to deal | 
|  | 6 in the Software without restriction, including without limitation the rights | 
|  | 7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
|  | 8 copies of the Software, and to permit persons to whom the Software is | 
|  | 9 furnished to do so, subject to the following conditions: | 
|  | 10 | 
|  | 11 The above copyright notice and this permission notice shall be included in | 
|  | 12 all copies or substantial portions of the Software. | 
|  | 13 | 
|  | 14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
|  | 15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
|  | 16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | 
|  | 17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
|  | 18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
|  | 19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | 
|  | 20 THE SOFTWARE. | 
|  | 21 */ | 
|  | 22 | 
|  | 23 package fschmidt.util.java; | 
|  | 24 | 
|  | 25 /** | 
|  | 26  * Base64 encoding and decoding (MIME spec RFC 1521). | 
|  | 27  */ | 
|  | 28 public class Base64 { | 
|  | 29 | 
|  | 30 	/** | 
|  | 31 	 * Stand alone test | 
|  | 32 	 */ | 
|  | 33 	public static void main(String[] args) | 
|  | 34 		throws Exception { | 
|  | 35 		if (args.length < 2) { | 
|  | 36 			System.out.println("Usage: Base64 <encode/decode> <inString>"); | 
|  | 37 			System.exit(0); | 
|  | 38 		} | 
|  | 39 | 
|  | 40 		String inString = args[1]; | 
|  | 41 		System.out.println("InString = " + inString); | 
|  | 42 | 
|  | 43 		if (args[0].equalsIgnoreCase("encode")) { | 
|  | 44 | 
|  | 45 		    String encodedString = Base64.encode(inString.getBytes()); | 
|  | 46 		    System.out.println("EncodedString = " + encodedString); | 
|  | 47 | 
|  | 48 		} else { | 
|  | 49             String outString = new String(Base64.decode(inString)); | 
|  | 50 		    System.out.println("DecodedString = " + outString); | 
|  | 51 		} | 
|  | 52 	} | 
|  | 53 | 
|  | 54 	/** | 
|  | 55 	 * Base64-encode binary data. | 
|  | 56 	 * | 
|  | 57 	 * @param bytes A buffer holding the data to encode. | 
|  | 58 	 * @return String String containing the encoded data | 
|  | 59 	 */ | 
|  | 60 	public static String encode(byte[] bytes) { | 
|  | 61 		return encode(bytes, false); | 
|  | 62 	} | 
|  | 63 | 
|  | 64 	/** | 
|  | 65 	 * base64-encode binary data. | 
|  | 66 	 * | 
|  | 67 	 * @param bytes A buffer holding the data to encode. | 
|  | 68 	 * @param lineBreaks true if the encoded data will be broken | 
|  | 69 	 *				  into 64-character lines with CRLF pairs. | 
|  | 70 	 * @return	String String containing the encoded data | 
|  | 71 	 */ | 
|  | 72 	public static String encode(byte[] bytes, | 
|  | 73 								boolean lineBreaks) { | 
|  | 74 | 
|  | 75 		/* slightly bigger buffer than we usually need */ | 
|  | 76 		StringBuilder buf= new StringBuilder((int)(bytes.length * 1.4)); | 
|  | 77 | 
|  | 78     	int temp			= 0;	/* holder for the current group of 24 bits */ | 
|  | 79     	int charsWritten	= 0;	/* count of char written (to current line if lineBreaks == true) */ | 
|  | 80     	int bytesRead		= 0;	/* count of bytes read in the current 24-bit group */ | 
|  | 81     	for(int index = 0; index < bytes.length; ++index) { | 
|  | 82 | 
|  | 83 			temp <<= 8; | 
|  | 84 			temp |= (((int)bytes[index]) & EIGHT_BITS); | 
|  | 85 			++bytesRead; | 
|  | 86 			if(bytesRead == 3) { | 
|  | 87 				/* The lower 3 bytes of temp now hold the 24 bits of a | 
|  | 88 				 * translation group. Write them out as 4 characters | 
|  | 89 				 * that represent 6 bits each. | 
|  | 90 				 */ | 
|  | 91 				buf.append(valueToChar[(temp >> 18) & SIX_BITS]); | 
|  | 92 				buf.append(valueToChar[(temp >> 12) & SIX_BITS]); | 
|  | 93 				buf.append(valueToChar[(temp >>  6) & SIX_BITS]); | 
|  | 94 				buf.append(valueToChar[(temp >>  0) & SIX_BITS]); | 
|  | 95 	    		temp = 0; | 
|  | 96 	    		bytesRead = 0; | 
|  | 97 	    		charsWritten += 4; | 
|  | 98    	    		if(lineBreaks && charsWritten >= 64) { | 
|  | 99 					buf.append(CRLF); | 
|  | 100 					charsWritten = 0; | 
|  | 101 				} | 
|  | 102 	    	} | 
|  | 103 		} | 
|  | 104 | 
|  | 105 		// The input byte[] is exhaused. | 
|  | 106 		// If there were fewer than 3 bytes in the last translation group, | 
|  | 107 		// we must complete the output and pad it to a multiple of 4 chars. | 
|  | 108 | 
|  | 109     	switch(bytesRead){ | 
|  | 110 			case 1: | 
|  | 111 				// The low 8 bits of temp take 2 characters to represent. | 
|  | 112 	    		buf.append(valueToChar[(temp >> 2) & SIX_BITS]); | 
|  | 113 	    		buf.append(valueToChar[(temp << 4) & SIX_BITS]); | 
|  | 114 				buf.append('='); // padding | 
|  | 115 				buf.append('='); // padding | 
|  | 116 				if(lineBreaks) { | 
|  | 117 					buf.append(CRLF); | 
|  | 118 				} | 
|  | 119 	    		break; | 
|  | 120 | 
|  | 121 			case 2: | 
|  | 122 				// The low 16 bits of temp take 3 characters to represent | 
|  | 123 	    		buf.append(valueToChar[(temp >> 10) & SIX_BITS]); | 
|  | 124 	    		buf.append(valueToChar[(temp >>  4) & SIX_BITS]); | 
|  | 125 	    		buf.append(valueToChar[(temp <<  2) & SIX_BITS]); | 
|  | 126 				buf.append('='); // padding | 
|  | 127 				if(lineBreaks){ | 
|  | 128 					buf.append(CRLF); | 
|  | 129 				} | 
|  | 130 	    		break; | 
|  | 131 | 
|  | 132 			default: | 
|  | 133 				// There were exactly 3 bytes in the last group. No padding needed. | 
|  | 134 	    		break; | 
|  | 135 		} | 
|  | 136 	    if(charsWritten > 0 && lineBreaks) { | 
|  | 137 			buf.append(CRLF); | 
|  | 138 		} | 
|  | 139 		return buf.toString(); | 
|  | 140 	} | 
|  | 141 | 
|  | 142 	/** | 
|  | 143 	 * Decode a base64-encoded string into binary data.<P> | 
|  | 144 	 * | 
|  | 145 	 * Ignores (skips over) carriage-return and line-feed characters. | 
|  | 146 	 * Stops decoding when it encounters the base64 pad character ('=') | 
|  | 147 	 * or when characters are encountered that are not MIME/base64 numerals. | 
|  | 148 	 * | 
|  | 149 	 * @param		mimeStr a String containing the base64-encoded data | 
|  | 150 	 * @return		a byte[] containing the decoded binary data. | 
|  | 151 	 * @exception	RuntimeException if the mimeStr parameter violates | 
|  | 152 	 *					the base64 encoding rules. | 
|  | 153 	 */ | 
|  | 154 	public static byte[] decode(String mimeStr) throws RuntimeException { | 
|  | 155 | 
|  | 156 		int		inputLength		= mimeStr.length(); | 
|  | 157 		byte	decoded[]		= new byte[inputLength]; | 
|  | 158 		int		temp			= 0;	// accumulator for 24-bit translation group | 
|  | 159 		int 	charsRead		= 0;	// characters read in current translation group | 
|  | 160 		int		bytesWritten	= 0;	// count of bytes written into decoded[] | 
|  | 161 		char	currChar;				// current character from mimeStr | 
|  | 162 		byte	currByte;				// base64 value represented by currChar | 
|  | 163 		int		equalCount		= 0;	// The number of '=' characters we've encountered. | 
|  | 164 | 
|  | 165 		for (int i = 0; i < inputLength; ++i) { | 
|  | 166 			currChar = mimeStr.charAt(i); | 
|  | 167 			if (currChar == CR || currChar == LF) { | 
|  | 168 				//Skip over carriage return or line-feed character. | 
|  | 169 				continue; | 
|  | 170 			} | 
|  | 171 			try { | 
|  | 172 				currByte = charToValue[(byte)currChar]; | 
|  | 173 			} catch(ArrayIndexOutOfBoundsException obe) { | 
|  | 174 				currByte = BAD_VAL; | 
|  | 175 			} | 
|  | 176 			if (currByte == BAD_VAL) { | 
|  | 177 				if (charsRead == 0) { | 
|  | 178 					 /* | 
|  | 179 					  * First character of a translation group is a | 
|  | 180 					  * non-base64 character.  We have presumably | 
|  | 181 					  * reached the end of base64 encoded data. | 
|  | 182 					  */ | 
|  | 183 					break; | 
|  | 184 				} else if (currChar == '=' & charsRead >= 2) { | 
|  | 185 					// Only the 3d and 4th characters may be '=' | 
|  | 186 					currByte = 0; | 
|  | 187 					++equalCount; | 
|  | 188 				} else { | 
|  | 189 					//String[] errArgs = new String[]{"Base64"}; | 
|  | 190 					throw new InvalidCharEncodedStringException(); //RuntimeException("E_INVALID_CHAR_ENCODED_STRING"); | 
|  | 191 				} | 
|  | 192 			} | 
|  | 193 			temp <<= 6; | 
|  | 194 			temp |= currByte; | 
|  | 195 			++charsRead; | 
|  | 196 			if (charsRead == 4) { | 
|  | 197 				/* | 
|  | 198 				 * temp has all 24 bits of the translation group, | 
|  | 199 				 * write the bytes to decode[] | 
|  | 200 				 */ | 
|  | 201 				decoded[bytesWritten++]= (byte)(temp >> 16); | 
|  | 202 				if(equalCount < 2) { | 
|  | 203 					decoded[bytesWritten++]= (byte)(temp >> 8); | 
|  | 204 					if(equalCount < 1) { | 
|  | 205 						decoded[bytesWritten++]= (byte)(temp >> 0); | 
|  | 206 					} | 
|  | 207 				} | 
|  | 208 				charsRead = 0; | 
|  | 209 				temp = 0; | 
|  | 210 				if (equalCount > 0) { | 
|  | 211 					// Encoded data is not permitted after an '=', we must be done. | 
|  | 212 					break; | 
|  | 213 				} | 
|  | 214 			} | 
|  | 215 		}	// end for == end of string | 
|  | 216 | 
|  | 217 		// Trim array to exact length | 
|  | 218 		byte result[] = new byte[bytesWritten]; | 
|  | 219 		System.arraycopy(decoded, 0, result, 0, bytesWritten); | 
|  | 220 		return result; | 
|  | 221 	} | 
|  | 222 | 
|  | 223 	private static final byte BAD_VAL = (byte)0xFF; | 
|  | 224 | 
|  | 225 	private static final int SIX_BITS = 0x3F; | 
|  | 226 	private static final int EIGHT_BITS = 0xFF; | 
|  | 227 | 
|  | 228 	private static final char CR = '\r'; | 
|  | 229 	private static final char LF = '\n'; | 
|  | 230 | 
|  | 231 	private static final String CRLF = "\r\n"; | 
|  | 232 | 
|  | 233 	/** | 
|  | 234 	 * Value to be encoded is an index into this array, so the | 
|  | 235 	 * character representation of byte value = valueToChar[value] | 
|  | 236 	 */ | 
|  | 237 	private static final char valueToChar[] = { | 
|  | 238 		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', | 
|  | 239 		'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', | 
|  | 240 		'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', | 
|  | 241 		'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', | 
|  | 242 		'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', | 
|  | 243 		'o', 'p', 'q', 'r', 's', 't', 'u', 'v', | 
|  | 244 		'w', 'x', 'y', 'z', '0', '1', '2', '3', | 
|  | 245 		'4', '5', '6', '7', '8', '9', '+', '/' | 
|  | 246 	}; | 
|  | 247 | 
|  | 248 	/** | 
|  | 249 	 * The ASCII value of a character is an index into this array, so that | 
|  | 250 	 * decoded byte value = charToValue[(byte)(character)]. <P> | 
|  | 251 	 * | 
|  | 252 	 * Characters that are not valid base64 numerals are detected | 
|  | 253 	 * one of two ways: either the expression above yeilds BAD_VAL | 
|  | 254 	 * or the array access gives an ArrayIndexOutOfBoundsException. <P> | 
|  | 255 	 * | 
|  | 256 	 */ | 
|  | 257 	private static final byte charToValue[] = { | 
|  | 258 		BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL, | 
|  | 259 		BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL, | 
|  | 260 		BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL, | 
|  | 261 		BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL, | 
|  | 262 		BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL, | 
|  | 263 		BAD_VAL,	BAD_VAL,	BAD_VAL,	62,			BAD_VAL,	BAD_VAL,	BAD_VAL,	63, | 
|  | 264 		52,			53,			54,			55,			56,			57,			58,			59, | 
|  | 265 		60,			61,			BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL, | 
|  | 266 		BAD_VAL,	0,			1,			2,			3,			4,			5,			6, | 
|  | 267 		7,			8,			9,			10,			11,			12,			13,			14, | 
|  | 268 		15,			16,			17,			18,			19,			20,			21,			22, | 
|  | 269 		23,			24,			25,			BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL, | 
|  | 270 		BAD_VAL,	26,			27,			28,			29,			30,			31,			32, | 
|  | 271 		33,			34,			35,			36,			37,			38,			39,			40, | 
|  | 272 		41,			42,			43,			44,			45,			46,			47,			48, | 
|  | 273 		49,			50,			51,			BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL | 
|  | 274 	}; | 
|  | 275 | 
|  | 276 	public static final class InvalidCharEncodedStringException extends RuntimeException {} | 
|  | 277 | 
|  | 278 } |