diff src/fschmidt/util/java/Base64.java @ 68:00520880ad02

add fschmidt source
author Franklin Schmidt <fschmidt@gmail.com>
date Sun, 05 Oct 2025 17:24:15 -0600
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/fschmidt/util/java/Base64.java	Sun Oct 05 17:24:15 2025 -0600
@@ -0,0 +1,278 @@
+/*
+Copyright (c) 2008  Franklin Schmidt <fschmidt@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+package fschmidt.util.java;
+
+/**
+ * Base64 encoding and decoding (MIME spec RFC 1521).
+ */
+public class Base64 {
+
+	/**
+	 * Stand alone test
+	 */
+	public static void main(String[] args) 
+		throws Exception {
+		if (args.length < 2) {
+			System.out.println("Usage: Base64 <encode/decode> <inString>");
+			System.exit(0);
+		}
+		
+		String inString = args[1]; 
+		System.out.println("InString = " + inString);
+		
+		if (args[0].equalsIgnoreCase("encode")) {
+		       
+		    String encodedString = Base64.encode(inString.getBytes());
+		    System.out.println("EncodedString = " + encodedString);    
+		    
+		} else {
+            String outString = new String(Base64.decode(inString));		
+		    System.out.println("DecodedString = " + outString);
+		}	
+	}
+	
+	/**
+	 * Base64-encode binary data.
+	 *
+	 * @param bytes A buffer holding the data to encode.
+	 * @return String String containing the encoded data
+	 */
+	public static String encode(byte[] bytes) {
+		return encode(bytes, false);
+	}
+
+	/**
+	 * base64-encode binary data.
+	 *
+	 * @param bytes A buffer holding the data to encode.
+	 * @param lineBreaks true if the encoded data will be broken
+	 *				  into 64-character lines with CRLF pairs.
+	 * @return	String String containing the encoded data
+	 */
+	public static String encode(byte[] bytes, 
+								boolean lineBreaks) {
+
+		/* slightly bigger buffer than we usually need */
+		StringBuilder buf= new StringBuilder((int)(bytes.length * 1.4));
+
+    	int temp			= 0;	/* holder for the current group of 24 bits */
+    	int charsWritten	= 0;	/* count of char written (to current line if lineBreaks == true) */
+    	int bytesRead		= 0;	/* count of bytes read in the current 24-bit group */
+    	for(int index = 0; index < bytes.length; ++index) {
+    		
+			temp <<= 8;
+			temp |= (((int)bytes[index]) & EIGHT_BITS);
+			++bytesRead;
+			if(bytesRead == 3) {
+				/* The lower 3 bytes of temp now hold the 24 bits of a 
+				 * translation group. Write them out as 4 characters 
+				 * that represent 6 bits each.
+				 */
+				buf.append(valueToChar[(temp >> 18) & SIX_BITS]);
+				buf.append(valueToChar[(temp >> 12) & SIX_BITS]);
+				buf.append(valueToChar[(temp >>  6) & SIX_BITS]);
+				buf.append(valueToChar[(temp >>  0) & SIX_BITS]);
+	    		temp = 0;
+	    		bytesRead = 0;
+	    		charsWritten += 4;
+   	    		if(lineBreaks && charsWritten >= 64) {
+					buf.append(CRLF);
+					charsWritten = 0;
+				}
+	    	}
+		}
+
+		// The input byte[] is exhaused.  
+		// If there were fewer than 3 bytes in the last translation group,
+		// we must complete the output and pad it to a multiple of 4 chars.
+		
+    	switch(bytesRead){
+			case 1:
+				// The low 8 bits of temp take 2 characters to represent. 
+	    		buf.append(valueToChar[(temp >> 2) & SIX_BITS]);
+	    		buf.append(valueToChar[(temp << 4) & SIX_BITS]);
+				buf.append('='); // padding 
+				buf.append('='); // padding 
+				if(lineBreaks) {
+					buf.append(CRLF);
+				}
+	    		break;
+
+			case 2:
+				// The low 16 bits of temp take 3 characters to represent 
+	    		buf.append(valueToChar[(temp >> 10) & SIX_BITS]);
+	    		buf.append(valueToChar[(temp >>  4) & SIX_BITS]);
+	    		buf.append(valueToChar[(temp <<  2) & SIX_BITS]);
+				buf.append('='); // padding 
+				if(lineBreaks){
+					buf.append(CRLF);
+				}
+	    		break;
+
+			default:
+				// There were exactly 3 bytes in the last group. No padding needed. 
+	    		break;
+		}
+	    if(charsWritten > 0 && lineBreaks) {
+			buf.append(CRLF);
+		}
+		return buf.toString();
+	}
+
+	/**
+	 * Decode a base64-encoded string into binary data.<P>
+	 * 
+	 * Ignores (skips over) carriage-return and line-feed characters.
+	 * Stops decoding when it encounters the base64 pad character ('=')
+	 * or when characters are encountered that are not MIME/base64 numerals.
+	 * 
+	 * @param		mimeStr a String containing the base64-encoded data
+	 * @return		a byte[] containing the decoded binary data.
+	 * @exception	RuntimeException if the mimeStr parameter violates 
+	 *					the base64 encoding rules.
+	 */
+	public static byte[] decode(String mimeStr) throws RuntimeException {
+
+		int		inputLength		= mimeStr.length();
+		byte	decoded[]		= new byte[inputLength];
+		int		temp			= 0;	// accumulator for 24-bit translation group 
+		int 	charsRead		= 0;	// characters read in current translation group 
+		int		bytesWritten	= 0;	// count of bytes written into decoded[] 
+		char	currChar;				// current character from mimeStr 
+		byte	currByte;				// base64 value represented by currChar 
+		int		equalCount		= 0;	// The number of '=' characters we've encountered. 
+
+		for (int i = 0; i < inputLength; ++i) {
+			currChar = mimeStr.charAt(i);
+			if (currChar == CR || currChar == LF) {
+				//Skip over carriage return or line-feed character. 
+				continue;
+			}
+			try {
+				currByte = charToValue[(byte)currChar];
+			} catch(ArrayIndexOutOfBoundsException obe) {
+				currByte = BAD_VAL;
+			}
+			if (currByte == BAD_VAL) {
+				if (charsRead == 0) {
+					 /*
+					  * First character of a translation group is a 
+					  * non-base64 character.  We have presumably 
+					  * reached the end of base64 encoded data. 
+					  */
+					break;
+				} else if (currChar == '=' & charsRead >= 2) {
+					// Only the 3d and 4th characters may be '=' 
+					currByte = 0;
+					++equalCount;
+				} else {
+					//String[] errArgs = new String[]{"Base64"};
+					throw new InvalidCharEncodedStringException(); //RuntimeException("E_INVALID_CHAR_ENCODED_STRING");
+				}
+			}
+			temp <<= 6;
+			temp |= currByte;
+			++charsRead;
+			if (charsRead == 4) {
+				/* 
+				 * temp has all 24 bits of the translation group, 
+				 * write the bytes to decode[]
+				 */
+				decoded[bytesWritten++]= (byte)(temp >> 16);
+				if(equalCount < 2) {
+					decoded[bytesWritten++]= (byte)(temp >> 8);
+					if(equalCount < 1) {
+						decoded[bytesWritten++]= (byte)(temp >> 0);
+					}
+				}
+				charsRead = 0;
+				temp = 0;
+				if (equalCount > 0) {
+					// Encoded data is not permitted after an '=', we must be done. 
+					break;
+				}
+			}
+		}	// end for == end of string 
+
+		// Trim array to exact length 
+		byte result[] = new byte[bytesWritten];
+		System.arraycopy(decoded, 0, result, 0, bytesWritten);
+		return result;
+	}
+
+	private static final byte BAD_VAL = (byte)0xFF;
+
+	private static final int SIX_BITS = 0x3F;
+	private static final int EIGHT_BITS = 0xFF;
+
+	private static final char CR = '\r';
+	private static final char LF = '\n';
+
+	private static final String CRLF = "\r\n";
+
+	/**
+	 * Value to be encoded is an index into this array, so the 
+	 * character representation of byte value = valueToChar[value]
+	 */
+	private static final char valueToChar[] = {
+		'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', '+', '/'
+	};
+
+	/**
+	 * The ASCII value of a character is an index into this array, so that 
+	 * decoded byte value = charToValue[(byte)(character)]. <P>
+	 * 
+	 * Characters that are not valid base64 numerals are detected
+	 * one of two ways: either the expression above yeilds BAD_VAL
+	 * or the array access gives an ArrayIndexOutOfBoundsException. <P>
+	 *
+	 */
+	private static final byte charToValue[] = {
+		BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	
+		BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	
+		BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	
+		BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	
+		BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	
+		BAD_VAL,	BAD_VAL,	BAD_VAL,	62,			BAD_VAL,	BAD_VAL,	BAD_VAL,	63,	
+		52,			53,			54,			55,			56,			57,			58,			59,	
+		60,			61,			BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	
+		BAD_VAL,	0,			1,			2,			3,			4,			5,			6,	
+		7,			8,			9,			10,			11,			12,			13,			14,	
+		15,			16,			17,			18,			19,			20,			21,			22,	
+		23,			24,			25,			BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	
+		BAD_VAL,	26,			27,			28,			29,			30,			31,			32,	
+		33,			34,			35,			36,			37,			38,			39,			40,	
+		41,			42,			43,			44,			45,			46,			47,			48,	
+		49,			50,			51,			BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL,	BAD_VAL
+	};
+
+	public static final class InvalidCharEncodedStringException extends RuntimeException {}
+
+}