Mega Code Archive

 
Categories / Java / File Input Output
 

This class provides encoding of byte arrays into Base64-encoded strings, and decoding the other way

package freenet.support; /**  * This class provides encoding of byte arrays into Base64-encoded strings,  * and decoding the other way.  *  * <P>NOTE!  This is modified Base64 with slightly different characters than  * usual, so it won't require escaping when used in URLs.  *  * <P>NOTE!  This class only does the padding that's normal in Base64  * if the 'true' flag is given to the encode() method.  This is because  * Base64 requires that the length of the encoded text be a multiple  * of four characters, padded with '='.  Without the 'true' flag, we don't  * add these '=' characters.  *  * @author Stephen Blackheath  */ public class Base64 {   private static char[] base64Alphabet = {     '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', '~', '-'};      private static char[] base64StandardAlphabet = {       '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', '+', '/'};   /**    * A reverse lookup table to convert base64 letters back into the    * a 6-bit sequence.    */   private static byte[] base64Reverse;   private static byte[] base64StandardReverse;       // Populate the base64Reverse lookup table from the base64Alphabet table.   static {     base64Reverse = new byte[128];     base64StandardReverse = new byte[base64Reverse.length];            // Set all entries to 0xFF, which means that that particular letter       // is not a legal base64 letter.     for (int i = 0; i < base64Reverse.length; i++) {       base64Reverse[i] = (byte) 0xFF;       base64StandardReverse[i] = (byte) 0xFF;     }     for (int i = 0; i < base64Alphabet.length; i++) {       base64Reverse[base64Alphabet[i]] = (byte) i;       base64StandardReverse[base64StandardAlphabet[i]] = (byte) i;     }   }   /**    * Encode to our shortened (non-standards-compliant) format.    */   public static String encode(byte[] in)   {     return encode(in, false);   }   /* FIXME: Figure out where this function is used and maybe remove it if its not    * used. Its old javadoc which has been here for a while fools the user into believing    * that the format is standard compliant */      /**    * Caller should specify equalsPad=true if they want a standards compliant padding,    * but not standard compliant encoding.    */   public static String encode(byte[] in, boolean equalsPad) {     return encode(in, equalsPad, base64Alphabet);   }      /**    * Standard compliant encoding.    */   public static String encodeStandard(byte[] in) {     return encode(in, true, base64StandardAlphabet);   }      /**    * Caller should specify equalsPad=true if they want a standards compliant encoding.    */   private static String encode(byte[] in, boolean equalsPad, char[] alphabet)   {     char[] out = new char[((in.length+2)/3)*4];     int rem = in.length%3;     int o = 0;     for (int i = 0; i < in.length;) {       int val = (in[i++] & 0xFF) << 16;       if (i < in.length)         val |= (in[i++] & 0xFF) << 8;       if (i < in.length)         val |= (in[i++] & 0xFF);       out[o++] = alphabet[(val>>18) & 0x3F];       out[o++] = alphabet[(val>>12) & 0x3F];       out[o++] = alphabet[(val>>6) & 0x3F];       out[o++] = alphabet[val & 0x3F];     }     int outLen = out.length;     switch (rem) {       case 1: outLen -= 2; break;       case 2: outLen -= 1; break;     }       // Pad with '=' signs up to a multiple of four if requested.     if (equalsPad)       while (outLen < out.length)         out[outLen++] = '=';     return new String(out, 0, outLen);   }   /**    * Handles the standards-compliant padding (padded with '=' signs) as well as our    * shortened form.  * @throws IllegalBase64Exception     */   public static byte[] decode(String inStr) throws Exception {     return decode(inStr, base64Reverse);   }      /**    * Handles the standards-compliant base64 encoding.    */   public static byte[] decodeStandard(String inStr) throws Exception {     return decode(inStr, base64StandardReverse);   }   /**    * Handles the standards-compliant (padded with '=' signs) as well as our    * shortened form.    */   private static byte[] decode(String inStr, byte[] reverseAlphabet)     throws Exception   {     try {       char[] in = inStr.toCharArray();       int inLength = in.length;         // Strip trailing equals signs.       while ((inLength > 0) && (in[inLength-1] == '='))         inLength--;       int blocks = inLength/4;       int remainder = inLength & 3;         // wholeInLen and wholeOutLen are the the length of the input and output         // sequences respectively, not including any partial block at the end.       int wholeInLen  = blocks*4;       int wholeOutLen = blocks*3;       int outLen = wholeOutLen;       switch (remainder) {         case 1: throw new Exception("illegal Base64 length");         case 2:  outLen = wholeOutLen+1; break;         case 3:  outLen = wholeOutLen+2; break;         default: outLen = wholeOutLen;       }       byte[] out = new byte[outLen];       int o = 0;       int i;       for (i = 0; i < wholeInLen;) {         int in1 = reverseAlphabet[in[i]];         int in2 = reverseAlphabet[in[i+1]];         int in3 = reverseAlphabet[in[i+2]];         int in4 = reverseAlphabet[in[i+3]];         int orValue = in1|in2|in3|in4;         if ((orValue & 0x80) != 0)           throw new Exception("illegal Base64 character");         int outVal = (in1 << 18) | (in2 << 12) | (in3 << 6) | in4;         out[o] = (byte) (outVal>>16);         out[o+1] = (byte) (outVal>>8);         out[o+2] = (byte) outVal;          i += 4;         o += 3;       }       int orValue;       switch (remainder) {         case 2:           {             int in1 = reverseAlphabet[in[i]];             int in2 = reverseAlphabet[in[i+1]];             orValue = in1|in2;             int outVal = (in1 << 18) | (in2 << 12);             out[o] = (byte) (outVal>>16);           }           break;         case 3:           {             int in1 = reverseAlphabet[in[i]];             int in2 = reverseAlphabet[in[i+1]];             int in3 = reverseAlphabet[in[i+2]];             orValue = in1|in2|in3;             int outVal = (in1 << 18) | (in2 << 12) | (in3 << 6);             out[o] = (byte) (outVal>>16);             out[o+1] = (byte) (outVal>>8);           }           break;         default:             // Keep compiler happy           orValue = 0;       }       if ((orValue & 0x80) != 0)         throw new Exception("illegal Base64 character");       return out;     }       // Illegal characters can cause an ArrayIndexOutOfBoundsException when       // looking up reverseAlphabet.     catch (ArrayIndexOutOfBoundsException e) {       throw new Exception("illegal Base64 character");     }   } } /*  * This program is free software; you can redistribute it and/or modify  * it under the terms of the GNU General Public License as published by  * the Free Software Foundation; either version 2 of the License, or  * (at your option) any later version.  *  * This program is distributed in the hope that it will be useful,  * but WITHOUT ANY WARRANTY; without even the implied warranty of  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  * GNU General Public License for more details.  *  * You should have received a copy of the GNU General Public License  * along with this program; if not, write to the Free Software  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */ package freenet.support; import java.util.Arrays; import java.util.Random; import junit.framework.TestCase; /**  * Test case for {@link freenet.support.Base64} class.  *   * @author Alberto Bacchelli &lt;sback@freenetproject.org&gt;  */ public class Base64Test extends TestCase {      /**    * Test the encode(byte[]) method    * against a well-known example    * (see http://en.wikipedia.org/wiki/Base_64 as reference)    * to verify if it encode works correctly.    */   public void testEncode() {     String toEncode = "Man is distinguished, not only by his reason, but by this singular " +         "passion from other animals, which is a lust of the mind, that by a perseverance " +         "of delight in the continued and indefatigable generation of knowledge, exceeds " +         "the short vehemence of any carnal pleasure.";     String expectedResult = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ" +         "1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIG" +         "x1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY" +         "29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRz" +         "IHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4";     byte[] aByteArrayToEncode = toEncode.getBytes();     assertEquals(Base64.encode(aByteArrayToEncode),expectedResult);   }      /**    * Test the decode(String) method    * against a well-known example    * (see http://en.wikipedia.org/wiki/Base_64 as reference)    * to verify if it decode an already encoded string correctly.    */     public void testDecode() {     String toDecode = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ" +         "1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIG" +         "x1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY" +         "29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRz" +         "IHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=";     String expectedResult = "Man is distinguished, not only by his reason, but by this singular " +         "passion from other animals, which is a lust of the mind, that by a perseverance " +         "of delight in the continued and indefatigable generation of knowledge, exceeds " +         "the short vehemence of any carnal pleasure.";     try {       String decodedString = new String(Base64.decode(toDecode));       assertEquals(decodedString,expectedResult);     } catch (IllegalBase64Exception aException) {       fail("Not expected exception thrown : " + aException.getMessage()); }   }      /**    * Test encode(byte[] in)    * and decode(String inStr) methods,    * to verify if they work correctly together.    * It compares the string before encoding    * and with the one after decoding.    */   public void testEncodeDecode() {     byte[] bytesDecoded;     byte[] bytesToEncode = new byte[5];          //byte upper bound     bytesToEncode[0] = 127;     bytesToEncode[1] = 64;     bytesToEncode[2] = 0;     bytesToEncode[3] = -64;     //byte lower bound     bytesToEncode[4] = -128;            String aBase64EncodedString = Base64.encode(bytesToEncode);          try {       bytesDecoded = Base64.decode(aBase64EncodedString);       assertTrue(Arrays.equals(bytesToEncode,bytesDecoded)); }      catch (IllegalBase64Exception aException) {       fail("Not expected exception thrown : " + aException.getMessage()); }   }      /**    * Test the encode(String,boolean)    * method to verify if the padding    * character '=' is correctly placed.    */   public void testEncodePadding() {     byte[][] methodBytesArray = {         //three byte Array -> no padding char expected         {4,4,4},             //two byte Array -> one padding char expected         {4,4},             //one byte Array -> two padding-chars expected           {4}};         String encoded;          for (int i = 0; i<methodBytesArray.length; i++) {       encoded = Base64.encode(methodBytesArray[i],true);       if (i == 0)         //no occurrences expected         assertEquals(encoded.indexOf('='),-1);       else         assertEquals(encoded.indexOf('='),encoded.length()-i);     }   }      /**    * Test if the decode(String) method    * raise correctly an exception when    * providing a string with non-Base64    * characters.    */   public void testIllegalBaseCharacter() { //    TODO: check many other possibile cases!     String illegalCharString = "abcd=fghilmn";     try {       Base64.decode(illegalCharString);       fail("Expected IllegalBase64Exception not thrown"); }     catch (IllegalBase64Exception exception) {       assertSame("illegal Base64 character",exception.getMessage()); }   }      /**    * Test if the decode(String) method    * raise correctly an exception when    * providing a string with a     * wrong Base64 length.    * (as we can consider not-padded strings too,    *  the only wrong lengths are the ones    *  where -> number MOD 4 = 1).    */   public void testIllegalBaseLength() {     //most interesting case     String illegalLengthString = "a";     try {       Base64.decode(illegalLengthString);       fail("Expected IllegalBase64Exception not thrown"); }     catch (IllegalBase64Exception exception) {       assertSame("illegal Base64 length",exception.getMessage()); }   }      /**    * Random test    *     * @throws IllegalBase64Exception    */   public void testRandom() throws IllegalBase64Exception {     int iter;     Random r = new Random(1234);     for (iter = 0; iter < 1000; iter++) {       byte[] b = new byte[r.nextInt(64)];       for (int i = 0; i < b.length; i++)         b[i] = (byte) (r.nextInt(256));       String encoded = Base64.encode(b);       byte[] decoded = Base64.decode(encoded);       assertEquals("length mismatch", decoded.length, b.length);       for (int i = 0; i < b.length; i++)         assertEquals("data mismatch: index " + i + " of " + b.length + " should be 0x"                 + Integer.toHexString(b[i] & 0xFF) + " was 0x" + Integer.toHexString(decoded[i] & 0xFF), b[i],                 decoded[i]);     }   } }