/*
* [Base64.java]
*
* Summary: Encode arbitrary binary into printable ASCII using BASE64 encoding.
*
* Copyright: (c) 1999-2017 Roedy Green, Canadian Mind Products, http://mindprod.com
*
* Licence: This software may be copied and used freely for any purpose but military.
* http://mindprod.com/contact/nonmil.html
*
* Requires: JDK 1.8+
*
* Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
*
* Version History:
* 1.0 1999-12-03 posted in comp.lang.java.programmer.
* 1.1 1999-12-04 more symmetrical encoding algorithm. more accurate StringBuffer allocation size.
* 1.2 2000-09-09 now handles decode as well.
* 1.3 2000-09-12 fix problems with estimating output length in encode
* 1.4 2002-02-15 correct bugs with uneven line lengths, allow you to configure line
* separator. now need Base64 object and instance methods. new mailing address.
* 1.5 2006-01-01
* 1.6 2007-01-01
* 1.7 2007-03-15 add Example
* 1.8 2007-03-15 tidy.
* 1.9 2007-05-20 add icon and pad
*/
package com.mindprod.base64;
import static java.lang.System.*;
/*
TODO Streams or files.
*/
/**
* Encode arbitrary binary into printable ASCII using BASE64 encoding.
*
* Base64 is a way of encoding 8-bit characters using only ASCII printable characters similar to UUENCODE. UUENCODE
* includes a filename where BASE64 does not. The spec is described in RFC 2045. Base64 is a scheme where 3 bytes are
* concatenated, then split to form 4 groups of 6-bits each; and each 6-bits gets translated to an encoded printable
* ASCII character, via a table lookup. An encoded string is therefore longer than the original by about 1/3. The
* "="
* character is used to pad the end. Base64 is used, among other things, to encode the user:password string in an
* Authorization: header for HTTP. Don't confuse Base64 with x-www-form-urlencoded which is handled by
* Java.net.URLEncoder.encode/decode
*
* If you don't like this code, there is another implementation at http://www.ruffboy.com/download.htm Sun has an
* undocumented method called sun.misc.Base64Encoder.encode. You could use hex, simpler to code, but not as compact.
*
* If you wanted to encode a giant file, you could do it in large chunks that are even multiples of 3 bytes, except for
* the last chunk, and append the outputs.
*
* To encode a string, rather than binary data java.net.URLEncoder may be better. See printable characters in the Java
* glossary for a discussion of the differences.
*
* Base 64 armouring uses only the characters A-Z \\p{Lower} 0-9 +/=. This makes it suitable for encoding binary data
* as SQL
* strings, that will work no matter what the encoding. Unfortunately + / and = all have special meaning in URLs.
*
* Works exactly like Base64 except avoids using the characters
* + / and =. This means Base64u-encoded data can be used either
* URLCoded or plain in
* URL-Encoded contexts such as GET, PUT or URLs. You can treat the
* output either as
* not needing encoding or already URLEncoded.
*
*
*
* Base64 ASCII armouring.
* Encode arbitrary binary into printable ASCII using BASE64 encoding. very loosely based on the Base64 Reader by: Dr.
* Mark Thornton
Optrak Distribution Software Ltd. http://www.optrak.co.uk and Kevin Kelley's
* http://www.ruralnet.net/~kelley/Java/Base64.java
*
* @author Roedy Green, Canadian Mind Products
* @version 1.9 2007-05-20 add icon and pad
* @since 1999-12-03
*/
public class Base64
{
/**
* used to disable test driver.
*
* @noinspection WeakerAccess
*/
protected static final boolean DEBUGGING = false;
/**
* Marker value for chars we just ignore, e.g. \n \r high ascii.
*
* @noinspection WeakerAccess
*/
protected static final int IGNORE = -1;
/**
* Marker for = trailing pad.
*
* @noinspection WeakerAccess
*/
protected static final int PAD = -2;
private static final int FIRST_COPYRIGHT_YEAR = 1999;
/**
* undisplayed copyright notice
*/
private static final String EMBEDDED_COPYRIGHT =
"Copyright: (c) 1999-2017 Roedy Green, Canadian Mind Products, http://mindprod.com";
/**
* when package was released.
*
* @noinspection UnusedDeclaration
*/
private static final String RELEASE_DATE = "2007-05-20";
/**
* name of package.
*
* @noinspection UnusedDeclaration
*/
private static final String TITLE_STRING = "Base64";
/**
* version of package.
*
* @noinspection UnusedDeclaration
*/
private static final String VERSION_STRING = "1.9";
/**
* binary value encoded by a given letter of the alphabet 0..63.
*
* @noinspection WeakerAccess
*/
protected static int[] cv;
/**
* letter of the alphabet used to encode binary values 0..63
*
* @noinspection WeakerAccess
*/
protected static char[] vc;
/**
* how we separate lines, e.g. \n, \r\n, \r etc.
*
* @noinspection WeakerAccess
*/
protected String lineSeparator = System.getProperty( "line.separator" );
/**
* letter of the alphabet used to encode binary values 0..63, overridden in Base64u.
*
* @noinspection WeakerAccess
*/
protected char[] valueToChar;
/**
* special character 1, will be - in Base64u.
*
* @noinspection WeakerAccess
*/
protected char spec1 = '+';
/**
* special character 2, will be _ in Base64u.
*
* @noinspection WeakerAccess
*/
protected char spec2 = '/';
/**
* special character 3, will be * in Base64u.
*
* @noinspection WeakerAccess
*/
protected char spec3 = '=';
/**
* binary value encoded by a given letter of the alphabet 0..63, overridden in Base64u.
*
* @noinspection WeakerAccess
*/
protected int[] charToValue;
/**
* max chars per line, excluding lineSeparator. A multiple of 4.
*
* @noinspection WeakerAccess
*/
protected int lineLength = 72;
/**
* constructor.
*
* @noinspection WeakerAccess
*/
public Base64()
{
spec1 = '+';
spec2 = '/';
spec3 = '=';
initTables();
}
/**
* Initialise both static and instance table.
*/
private void initTables()
{
/* initialise valueToChar and charToValue tables */
if ( vc == null )
{
// statics are not initialised yet
vc = new char[ 64 ];
cv = new int[ 256 ];
// build translate valueToChar table only once.
// 0..25 -> 'A'..'Z'
for ( int i = 0; i <= 25; i++ )
{
vc[ i ] = ( char ) ( 'A' + i );
}
// 26..51 -> 'a'..'z'
for ( int i = 0; i <= 25; i++ )
{
vc[ i + 26 ] = ( char ) ( 'a' + i );
}
// 52..61 -> '0'..'9'
for ( int i = 0; i <= 9; i++ )
{
vc[ i + 52 ] = ( char ) ( '0' + i );
}
vc[ 62 ] = spec1;
vc[ 63 ] = spec2;
// build translate charToValue table only once.
for ( int i = 0; i < 256; i++ )
{
cv[ i ] = IGNORE;// default is to ignore
}
for ( int i = 0; i < 64; i++ )
{
cv[ vc[ i ] ] = i;
}
cv[ spec3 ] = PAD;
}
valueToChar = vc;
charToValue = cv;
}
/**
* test driver.
*
* @param args not used
*
* @noinspection ConstantConditions
*/
public static void main( String[] args )
{
if ( DEBUGGING )
{
byte[] a = { ( byte ) 0xfc, ( byte ) 0x0f, ( byte ) 0xc0 };
byte[] b = { ( byte ) 0x03, ( byte ) 0xf0, ( byte ) 0x3f };
byte[] c = { ( byte ) 0x00, ( byte ) 0x00, ( byte ) 0x00 };
byte[] d = { ( byte ) 0xff, ( byte ) 0xff, ( byte ) 0xff };
byte[] e = { ( byte ) 0xfc, ( byte ) 0x0f, ( byte ) 0xc0, ( byte ) 1 };
byte[] f =
{ ( byte ) 0xfc, ( byte ) 0x0f, ( byte ) 0xc0, ( byte ) 1, ( byte ) 2 };
byte[] g = {
( byte ) 0xfc,
( byte ) 0x0f,
( byte ) 0xc0,
( byte ) 1,
( byte ) 2,
( byte ) 3 };
byte[] h = "AAAAAAAAAAB".getBytes();
show( a );
show( b );
show( c );
show( d );
show( e );
show( f );
show( g );
show( h );
Base64 b64 = new Base64();
show( b64.decode( b64.encode( a ) ) );
show( b64.decode( b64.encode( b ) ) );
show( b64.decode( b64.encode( c ) ) );
show( b64.decode( b64.encode( d ) ) );
show( b64.decode( b64.encode( e ) ) );
show( b64.decode( b64.encode( f ) ) );
show( b64.decode( b64.encode( g ) ) );
show( b64.decode( b64.encode( h ) ) );
b64.setLineLength( 8 );
show( ( b64.encode( h ) ).getBytes() );
}
} // end main
/**
* debug display array as hex.
*
* @param b byte array to display.
*
* @noinspection WeakerAccess
*/
public static void show( byte[] b )
{
for ( final byte aB : b )
{
out.print( Integer.toHexString( aB & 0xff ) + " " );
}
out.println();
}
/**
* decode a well-formed complete Base64 string back into an array of bytes. It must have an even multiple of 4 data
* characters (not counting \n), padded out with = as needed.
*
* @param s base64-encoded string
*
* @return plaintext as a byte array.
* @noinspection WeakerAccess, CanBeFinal
*/
@SuppressWarnings( "fallthrough" )
public byte[] decode( String s )
{
// estimate worst case size of output array, no embedded newlines.
byte[] b = new byte[ ( s.length() / 4 ) * 3 ];
// tracks where we are in a cycle of 4 input chars.
int cycle = 0;
// where we combine 4 groups of 6 bits and take apart as 3 groups of 8.
int combined = 0;
// how many bytes we have prepared.
int j = 0;
// will be an even multiple of 4 chars, plus some embedded \n
int len = s.length();
int dummies = 0;
for ( int i = 0; i < len; i++ )
{
int c = s.charAt( i );
int value = ( c <= 255 ) ? charToValue[ c ] : IGNORE;
// there are two magic values PAD (=) and IGNORE.
switch ( value )
{
case IGNORE:
// e.g. \n, just ignore it.
break;
case PAD:
value = 0;
dummies++;
// deliberate fallthrough
default:
/* regular value character */
switch ( cycle )
{
case 0:
combined = value;
cycle = 1;
break;
case 1:
combined <<= 6;
combined |= value;
cycle = 2;
break;
case 2:
combined <<= 6;
combined |= value;
cycle = 3;
break;
case 3:
combined <<= 6;
combined |= value;
// we have just completed a cycle of 4 chars.
// the four 6-bit values are in combined in
// big-endian order
// peel them off 8 bits at a time working lsb to msb
// to get our original 3 8-bit bytes back
b[ j + 2 ] = ( byte ) combined;
combined >>>= 8;
b[ j + 1 ] = ( byte ) combined;
combined >>>= 8;
b[ j ] = ( byte ) combined;
j += 3;
cycle = 0;
break;
}
break;
}
} // end for
if ( cycle != 0 )
{
throw new ArrayIndexOutOfBoundsException(
"Input to decode not an even multiple of 4 characters; pad with "
+ spec3
);
}
j -= dummies;
if ( b.length != j )
{
byte[] b2 = new byte[ j ];
System.arraycopy( b, 0, b2, 0, j );
b = b2;
}
return b;
} // end decode
/**
* Encode an arbitrary array of bytes as Base64 printable ASCII. It will be broken into lines of 72 chars each. The
* last line is not terminated with a line separator. The output will always have an even multiple of data
* characters, exclusive of \n. It is padded out with =.
*
* @param b byte array to encode, typically produced by a ByteArrayOutputStream.
*
* @return base-64 encoded String, not char[] or byte[].
* @noinspection WeakerAccess, CanBeFinal
*/
public String encode( byte[] b )
{
// Each group or partial group of 3 bytes becomes four chars
// covered quotient
int outputLength = ( ( b.length + 2 ) / 3 ) * 4;
// account for trailing newlines, on all but the very last line
if ( lineLength != 0 )
{
int lines = ( outputLength + lineLength - 1 ) / lineLength - 1;
if ( lines > 0 )
{
outputLength += lines * lineSeparator.length();
}
}
// must be local for recursion to work.
StringBuilder sb = new StringBuilder( outputLength );
// must be local for recursion to work.
int linePos = 0;
// first deal with even multiples of 3 bytes.
int len = ( b.length / 3 ) * 3;
int leftover = b.length - len;
for ( int i = 0; i < len; i += 3 )
{
// Start a new line if next 4 chars won't fit on the current line
// We can't encapsulete the following code since the variable need
// to
// be local to this incarnation of encode.
linePos += 4;
if ( linePos > lineLength )
{
if ( lineLength != 0 )
{
sb.append( lineSeparator );
}
// linePos = 4;
}
// get next three bytes in unsigned form lined up,
// in big-endian order
int combined = b[ i ] & 0xff;
combined <<= 8;
combined |= b[ i + 1 ] & 0xff;
combined <<= 8;
combined |= b[ i + 2 ] & 0xff;
// break those 24 bits into a 4 groups of 6 bits,
// working LSB to MSB.
int c3 = combined & 0x3f;
combined >>>= 6;
int c2 = combined & 0x3f;
combined >>>= 6;
int c1 = combined & 0x3f;
combined >>>= 6;
int c0 = combined & 0x3f;
// Translate into the equivalent alpha character
// emitting them in big-endian order.
sb.append( valueToChar[ c0 ] );
sb.append( valueToChar[ c1 ] );
sb.append( valueToChar[ c2 ] );
sb.append( valueToChar[ c3 ] );
}
// deal with leftover bytes
switch ( leftover )
{
case 0:
default:
// nothing to do
break;
case 1:
// One leftover byte generates xx==
// Start a new line if next 4 chars won't fit on the current
// line
linePos += 4;
if ( linePos > lineLength )
{
if ( lineLength != 0 )
{
sb.append( lineSeparator );
}
// linePos = 4;
}
// Handle this recursively with a faked complete triple.
// Throw away last two chars and replace with ==
sb.append( encode( new byte[] { b[ len ], 0, 0 } ).substring( 0,
2 ) );
sb.append( spec3 );
sb.append( spec3 );
break;
case 2:
// Two leftover bytes generates xxx=
// Start a new line if next 4 chars won't fit on the current
// line
linePos += 4;
if ( linePos > lineLength )
{
if ( lineLength != 0 )
{
sb.append( lineSeparator );
}
// linePos = 4;
}
// Handle this recursively with a faked complete triple.
// Throw away last char and replace with =
sb.append( encode( new byte[] {
b[ len ], b[ len + 1 ], 0 } ).substring( 0, 3 ) );
sb.append( spec3 );
break;
} // end switch;
if ( outputLength != sb.length() )
{
out.println(
"oops: minor program flaw: output length mis-estimated" );
out.println( "estimate:" + outputLength );
out.println( "actual:" + sb.length() );
}
return sb.toString();
} // end encode
/**
* determines how long the lines are that are generated by encode. Ignored by decode.
*
* @param length 0 means no newlines inserted. Must be a multiple of 4.
*
* @noinspection WeakerAccess
*/
public final void setLineLength( int length )
{
this.lineLength = ( length / 4 ) * 4;
}
/**
* How lines are separated. Ignored by decode.
*
* @param lineSeparator may be "" but not null. Usually contains only a combination of chars \n and \r. Could be any
* chars not in set A-Z \\p{Lower} 0-9 + /.
*/
public final void setLineSeparator( String lineSeparator )
{
this.lineSeparator = lineSeparator;
}
} // end Base64