
org.jppf.utils.base64.Base64Decoding Maven / Gradle / Ivy
Show all versions of jppf-common Show documentation
/*
* JPPF.
* Copyright (C) 2005-2015 JPPF Team.
* http://www.jppf.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jppf.utils.base64;
import static org.jppf.utils.base64.Base64.*;
import java.io.*;
import org.jppf.io.IO;
/**
*
* @author Laurent Cohen
*/
public final class Base64Decoding
{
/* ******** D E C O D I N G M E T H O D S ******** */
/**
* Decodes four bytes from array source and writes the resulting bytes (up to three of them) to destination.
* The source and destination arrays can be manipulated anywhere along their length by specifying srcOffset and destOffset.
* This method does not check to make sure your arrays are large enough to accomodate srcOffset + 4 for
* the source array or destOffset + 3 for the destination array.
* This method returns the actual number of bytes that were converted from the Base64 encoding.
* This is the lowest level of the decoding methods with all possible parameters.
* @param source the array to convert
* @param srcOffset the index where conversion begins
* @param destination the array to hold the conversion
* @param destOffset the index where output will be put
* @param options alphabet type is pulled from this (standard, url-safe, ordered)
* @return the number of decoded bytes converted
* @throws NullPointerException if source or destination arrays are null
* @throws IllegalArgumentException if srcOffset or destOffset are invalid or there is not enough room in the array.
* @since 1.3
*/
static int decode4to3(
final byte[] source, final int srcOffset,
final byte[] destination, final int destOffset, final int options ) {
// Lots of error checking and exception throwing
if( source == null ) throw new NullPointerException( "Source array was null." );
if( destination == null ) throw new NullPointerException( "Destination array was null." );
if( srcOffset < 0 || srcOffset + 3 >= source.length )
throw new IllegalArgumentException( String.format("Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) );
if( destOffset < 0 || destOffset +2 >= destination.length )
throw new IllegalArgumentException( String.format("Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) );
byte[] DECODABET = getDecodabet( options );
// Example: Dk==
if( source[ srcOffset + 2] == EQUALS_SIGN ) {
// Two ways to do the same thing. Don't know which way I like best.
//int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
// | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
| ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 );
destination[ destOffset ] = (byte)( outBuff >>> 16 );
return 1;
}
// Example: DkL=
else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) {
// Two ways to do the same thing. Don't know which way I like best.
//int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
// | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
// | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
| ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
| ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 );
destination[ destOffset ] = (byte)( outBuff >>> 16 );
destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 );
return 2;
}
// Example: DkLE
else {
// Two ways to do the same thing. Don't know which way I like best.
//int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
// | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
// | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
// | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
| ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
| ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6)
| ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) );
destination[ destOffset ] = (byte)( outBuff >> 16 );
destination[ destOffset + 1 ] = (byte)( outBuff >> 8 );
destination[ destOffset + 2 ] = (byte)( outBuff );
return 3;
}
} // end decodeToBytes
/**
* Low-level access to decoding ASCII characters in the form of a byte array. Ignores GUNZIP option, if
* it's set. This is not generally a recommended method, although it is used internally as part of the decoding process.
* Special case: if len = 0, an empty array is returned. Still, if you need more speed and reduced memory footprint (and aren't gzipping), consider this method.
* @param source The Base64 encoded data
* @return decoded data
* @throws IOException if any I/O error occurs.
* @since 2.3.1
*/
public static byte[] decode( final byte[] source ) throws IOException {
byte[] decoded = null;
decoded = decode( source, 0, source.length, Base64.NO_OPTIONS );
return decoded;
}
/**
* Low-level access to decoding ASCII characters in the form of a byte array. Ignores GUNZIP option, if
* it's set. This is not generally a recommended method, although it is used internally as part of the decoding process.
* Special case: if len = 0, an empty array is returned. Still, if you need more speed and reduced memory footprint (and aren't gzipping), consider this method.
* @param source The Base64 encoded data
* @param off The offset of where to begin decoding
* @param len The length of characters to decode
* @param options Can specify options such as alphabet type to use
* @return decoded data
* @throws IOException If bogus characters exist in source data
* @since 1.3
*/
public static byte[] decode( final byte[] source, final int off, final int len, final int options ) throws IOException {
// Lots of error checking and exception throwing
if( source == null ) throw new NullPointerException( "Cannot decode null source array." );
if( off < 0 || off + len > source.length )
throw new IllegalArgumentException( String.format("Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) );
if( len == 0 )return IO.EMPTY_BYTES;
else if( len < 4 ){
throw new IllegalArgumentException(
"Base64-encoded string must have at least four characters, but length specified was " + len );
} // end if
byte[] DECODABET = getDecodabet( options );
int len34 = len * 3 / 4; // Estimate on array size
byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output
int outBuffPosn = 0; // Keep track of where we're writing
byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space
int b4Posn = 0; // Keep track of four byte input buffer
int i = 0; // Source array counter
byte sbiDecode = 0; // Special value from DECODABET
for( i = off; i < off+len; i++ ) { // Loop through source
sbiDecode = DECODABET[ source[i]&0xFF ];
// White space, Equals sign, or legit Base64 character
// Note the values such as -5 and -9 in the DECODABETs at the top of the file.
if( sbiDecode >= WHITE_SPACE_ENC ) {
if( sbiDecode >= EQUALS_SIGN_ENC ) {
b4[ b4Posn++ ] = source[i]; // Save non-whitespace
if( b4Posn > 3 ) { // Time to decode?
outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options );
b4Posn = 0;
// If that was the equals sign, break out of 'for' loop
if( source[i] == EQUALS_SIGN )break;
} // end if: quartet built
} // end if: equals sign or better
} // end if: white space, equals sign or better
else {
// There's a bad input character in the Base64 stream.
throw new IOException( String.format("Bad Base64 input character decimal %d in array position %d", (source[i])&0xFF, i ) );
} // end else:
} // each input character
byte[] out = new byte[ outBuffPosn ];
System.arraycopy( outBuff, 0, out, 0, outBuffPosn );
return out;
} // end decode
/**
* Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it.
* @param s the string to decode
* @return the decoded data
* @throws IOException If there is a problem
* @since 1.4
*/
public static byte[] decode( final String s ) throws IOException {
return decode( s, NO_OPTIONS );
}
/**
* Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it.
* @param s the string to decode
* @param options encode options such as URL_SAFE
* @return the decoded data
* @throws IOException if there is an error
* @throws NullPointerException if s is null
* @since 1.4
*/
public static byte[] decode( final String s, final int options ) throws IOException {
if( s == null ) throw new NullPointerException( "Input string was null." );
byte[] bytes;
try {
bytes = s.getBytes( PREFERRED_ENCODING );
} // end try
catch( UnsupportedEncodingException uee ) {
bytes = s.getBytes();
} // end catch
// Decode
bytes = decode( bytes, 0, bytes.length, options );
// Check to see if it's gzip-compressed GZIP Magic Two-Byte Number: 0x8b1f (35615)
boolean dontGunzip = (options & DONT_GUNZIP) != 0;
if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) {
int head = (bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) {
ByteArrayInputStream bais = null;
java.util.zip.GZIPInputStream gzis = null;
ByteArrayOutputStream baos = null;
byte[] buffer = new byte[2048];
int length = 0;
try {
baos = new ByteArrayOutputStream();
bais = new ByteArrayInputStream( bytes );
gzis = new java.util.zip.GZIPInputStream( bais );
while( ( length = gzis.read( buffer ) ) >= 0 ) {
baos.write(buffer,0,length);
} // end while: reading input
// No error? Get new bytes.
bytes = baos.toByteArray();
} // end try
catch( IOException e ) {
e.printStackTrace();
// Just return originally-decoded bytes
} // end catch
finally {
try{ baos.close(); } catch( Exception e ){}
try{ gzis.close(); } catch( Exception e ){}
try{ bais.close(); } catch( Exception e ){}
} // end finally
} // end if: gzipped
} // end if: bytes.length >= 2
return bytes;
} // end decode
/**
* Attempts to decode Base64 data and deserialize a Java Object within. Returns null if there was an error.
* @param encodedObject The Base64 data to decode
* @return The decoded and deserialized object
* @throws NullPointerException if encodedObject is null
* @throws IOException if there is a general error
* @throws ClassNotFoundException if the decoded object is of a class that cannot be found by the JVM
* @since 1.5
*/
public static Object decodeToObject( final String encodedObject ) throws IOException, ClassNotFoundException {
return decodeToObject(encodedObject,NO_OPTIONS,null);
}
/**
* Attempts to decode Base64 data and deserialize a Java Object within. Returns null if there was an error.
* If loader is not null, it will be the class loader used when deserializing.
* @param encodedObject The Base64 data to decode
* @param options Various parameters related to decoding
* @param loader Optional class loader to use in deserializing classes.
* @return The decoded and deserialized object
* @throws NullPointerException if encodedObject is null
* @throws IOException if there is a general error
* @throws ClassNotFoundException if the decoded object is of a class that cannot be found by the JVM
* @since 2.3.4
*/
public static Object decodeToObject(final String encodedObject, final int options, final ClassLoader loader ) throws IOException, java.lang.ClassNotFoundException {
// Decode and gunzip if necessary
byte[] objBytes = decode( encodedObject, options );
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
Object obj = null;
try {
bais = new ByteArrayInputStream( objBytes );
// If no custom class loader is provided, use Java's builtin OIS.
if( loader == null ) ois = new ObjectInputStream( bais );
// Else make a customized object input stream that uses the provided class loader.
else {
ois = new ObjectInputStream(bais){
@Override
public Class> resolveClass(final ObjectStreamClass streamClass)
throws IOException, ClassNotFoundException {
Class c = Class.forName(streamClass.getName(), false, loader);
if( c == null ) return super.resolveClass(streamClass);
else return c; // Class loader knows of this class.
} // end resolveClass
}; // end ois
} // end else: no custom class loader
obj = ois.readObject();
} // end try
catch( IOException e ) {
throw e; // Catch and throw in order to execute finally{}
} // end catch
catch( java.lang.ClassNotFoundException e ) {
throw e; // Catch and throw in order to execute finally{}
} // end catch
finally {
try{ bais.close(); } catch( Exception e ){}
try{ ois.close(); } catch( Exception e ){}
} // end finally
return obj;
} // end decodeObject
/**
* Convenience method for encoding data to a file.
* As of v 2.3, if there is a error, the method will throw an IOException. This is new to v2.3!
* In earlier versions, it just returned false, but in retrospect that's a pretty poor way to handle it.
* @param dataToEncode byte array of data to encode in base64 form
* @param filename Filename for saving encoded data
* @throws IOException if there is an error
* @throws NullPointerException if dataToEncode is null
* @since 2.1
*/
public static void encodeToFile( final byte[] dataToEncode, final String filename )
throws IOException {
if( dataToEncode == null ) throw new NullPointerException( "Data to encode was null." );
Base64OutputStream bos = null;
try {
bos = new Base64OutputStream(
new FileOutputStream( filename ), Base64.ENCODE );
bos.write( dataToEncode );
} // end try
catch( IOException e ) {
throw e; // Catch and throw to execute finally{} block
} // end catch: IOException
finally {
try{ bos.close(); } catch( Exception e ){}
} // end finally
} // end encodeToFile
/**
* Convenience method for decoding data to a file.
* As of v 2.3, if there is a error, the method will throw an IOException. This is new to v2.3!
* In earlier versions, it just returned false, but in retrospect that's a pretty poor way to handle it.
* @param dataToDecode Base64-encoded data as a string
* @param filename Filename for saving decoded data
* @throws IOException if there is an error
* @since 2.1
*/
public static void decodeToFile( final String dataToDecode, final String filename) throws IOException {
Base64OutputStream bos = null;
try{
bos = new Base64OutputStream(new FileOutputStream( filename ), Base64.DECODE );
bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) );
} // end try
catch( IOException e ) {
throw e; // Catch and throw to execute finally{} block
} // end catch: IOException
finally {
try{ bos.close(); } catch( Exception e ){}
} // end finally
} // end decodeToFile
/**
* Convenience method for reading a base64-encoded file and decoding it.
* As of v 2.3, if there is a error, the method will throw an IOException. This is new to v2.3!
* In earlier versions, it just returned false, but in retrospect that's a pretty poor way to handle it.
* @param filename Filename for reading encoded data
* @return decoded byte array
* @throws IOException if there is an error
* @since 2.1
*/
public static byte[] decodeFromFile( final String filename )
throws IOException {
byte[] decodedData = null;
Base64InputStream bis = null;
try
{
// Set up some useful variables
File file = new File( filename );
byte[] buffer = null;
int length = 0;
int numBytes = 0;
// Check for size of file
if( file.length() > Integer.MAX_VALUE ) throw new IOException( "File is too big for this convenience method (" + file.length() + " bytes)." );
buffer = new byte[ (int)file.length() ];
// Open a stream
bis = new Base64InputStream(new BufferedInputStream(new FileInputStream( file ) ), Base64.DECODE );
// Read until done
while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 )length += numBytes;
// Save in a variable to return
decodedData = new byte[ length ];
System.arraycopy( buffer, 0, decodedData, 0, length );
} // end try
catch( IOException e ) {
throw e; // Catch and release to execute finally{}
} // end catch: IOException
finally {
try{ bis.close(); } catch( Exception e) {}
} // end finally
return decodedData;
} // end decodeFromFile
/**
* Convenience method for reading a binary file and base64-encoding it.
* As of v 2.3, if there is a error, the method will throw an IOException. This is new to v2.3!
* In earlier versions, it just returned false, but in retrospect that's a pretty poor way to handle it.
* @param filename Filename for reading binary data
* @return base64-encoded string
* @throws IOException if there is an error
* @since 2.1
*/
public static String encodeFromFile(final String filename) throws IOException {
String encodedData = null;
Base64InputStream bis = null;
try
{
File file = new File( filename );
byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4+1),40) ]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5)
int length = 0;
int numBytes = 0;
bis = new Base64InputStream(new BufferedInputStream(new FileInputStream( file ) ), Base64.ENCODE );
// Read until done
while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) length += numBytes;
// Save in a variable to return
encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING );
} // end try
catch( IOException e ) {
throw e; // Catch and release to execute finally{}
} // end catch: IOException
finally {
try{ bis.close(); } catch( Exception e) {}
} // end finally
return encodedData;
} // end encodeFromFile
/**
* Reads infile and encodes it to outfile.
* @param infile Input file
* @param outfile Output file
* @throws IOException if there is an error
* @since 2.2
*/
public static void encodeFileToFile( final String infile, final String outfile ) throws IOException {
String encoded = encodeFromFile( infile );
OutputStream out = null;
try{
out = new BufferedOutputStream(new FileOutputStream( outfile ) );
out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output.
} // end try
catch( IOException e ) {
throw e; // Catch and release to execute finally{}
} // end catch
finally {
try { out.close(); }
catch( Exception ex ){}
} // end finally
} // end encodeFileToFile
/**
* Reads infile and decodes it to outfile.
* @param infile Input file
* @param outfile Output file
* @throws IOException if there is an error
* @since 2.2
*/
public static void decodeFileToFile( final String infile, final String outfile ) throws IOException {
byte[] decoded = decodeFromFile( infile );
OutputStream out = null;
try{
out = new BufferedOutputStream(new FileOutputStream( outfile ) );
out.write( decoded );
} // end try
catch( IOException e ) {
throw e; // Catch and release to execute finally{}
} // end catch
finally {
try { out.close(); }
catch( Exception ex ){}
} // end finally
} // end decodeFileToFile
}