| 
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 }
 |