001    //////////////////////license & copyright header/////////////////////////
002    //                                                                     //
003    //    Base64 - encode/decode data using the Base64 encoding scheme     //
004    //                                                                     //
005    //                Copyright (c) 1998 by Kevin Kelley                   //
006    //                                                                     //
007    // This library is free software; you can redistribute it and/or       //
008    // modify it under the terms of the GNU Lesser General Public          //
009    // License as published by the Free Software Foundation; either        //
010    // version 2.1 of the License, or (at your option) any later version.  //
011    //                                                                     //
012    // This library is distributed in the hope that it will be useful,     //
013    // but WITHOUT ANY WARRANTY; without even the implied warranty of      //
014    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the       //
015    // GNU Lesser General Public License for more details.                 //
016    //                                                                     //
017    // You should have received a copy of the GNU Lesser General Public    //
018    // License along with this library; if not, write to the Free Software //
019    // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA           //
020    // 02111-1307, USA, or contact the author:                             //
021    //                                                                     //
022    // Kevin Kelley <kelley@ruralnet.net> - 30718 Rd. 28, La Junta, CO,    //
023    // 81050  USA.                                                         //
024    //                                                                     //
025    ////////////////////end license & copyright header///////////////////////
026    
027    package org.objectweb.jac.util;
028    
029    import java.io.*;       // needed only for main() method.
030    
031    
032    /**
033     * Provides encoding of raw bytes to base64-encoded characters, and
034     * decoding of base64 characters to raw bytes.
035     *
036     * @author Kevin Kelley (kelley@ruralnet.net)
037     * @version 1.3
038     */
039    public class Base64 {
040    
041       /**
042        * returns an array of base64-encoded characters to represent the
043        * passed data array.
044        *
045        * @param data the array of bytes to encode
046        * @return base64-coded character array.
047        */
048       static public char[] encode(byte[] data)
049       {
050          char[] out = new char[((data.length + 2) / 3) * 4];
051    
052          //
053          // 3 bytes encode to 4 chars.  Output is always an even
054          // multiple of 4 characters.
055          //
056          for (int i=0, index=0; i<data.length; i+=3, index+=4) {
057             boolean quad = false;
058             boolean trip = false;
059    
060             int val = (0xFF & (int) data[i]);
061             val <<= 8;
062             if ((i+1) < data.length) {
063                val |= (0xFF & (int) data[i+1]);
064                trip = true;
065             }
066             val <<= 8;
067             if ((i+2) < data.length) {
068                val |= (0xFF & (int) data[i+2]);
069                quad = true;
070             }
071             out[index+3] = alphabet[(quad? (val & 0x3F): 64)];
072             val >>= 6;
073             out[index+2] = alphabet[(trip? (val & 0x3F): 64)];
074             val >>= 6;
075             out[index+1] = alphabet[val & 0x3F];
076             val >>= 6;
077             out[index+0] = alphabet[val & 0x3F];
078          }
079          return out;
080       }
081    
082       public static String encodeToString(byte[] data) {
083          return new String(encode(data));
084       }
085    
086       /**
087        * Decodes a BASE-64 encoded stream to recover the original
088        * data. White space before and after will be trimmed away,
089        * but no other manipulation of the input will be performed.
090        *
091        * As of version 1.2 this method will properly handle input
092        * containing junk characters (newlines and the like) rather
093        * than throwing an error. It does this by pre-parsing the
094        * input and generating from that a count of VALID input
095        * characters.
096        **/
097       static public byte[] decode(char[] data)
098       {
099          // as our input could contain non-BASE64 data (newlines,
100          // whitespace of any sort, whatever) we must first adjust
101          // our count of USABLE data so that...
102          // (a) we don't misallocate the output array, and
103          // (b) think that we miscalculated our data length
104          //     just because of extraneous throw-away junk
105    
106          int tempLen = data.length;
107          for( int ix=0; ix<data.length; ix++ )
108          {
109             if( (data[ix] > 255) || codes[ data[ix] ] < 0 )
110                --tempLen;  // ignore non-valid chars and padding
111          }
112          // calculate required length:
113          //  -- 3 bytes for every 4 valid base64 chars
114          //  -- plus 2 bytes if there are 3 extra base64 chars,
115          //     or plus 1 byte if there are 2 extra.
116    
117          int len = (tempLen / 4) * 3;
118          if ((tempLen % 4) == 3) len += 2;
119          if ((tempLen % 4) == 2) len += 1;
120    
121          byte[] out = new byte[len];
122    
123    
124    
125          int shift = 0;   // # of excess bits stored in accum
126          int accum = 0;   // excess bits
127          int index = 0;
128    
129          // we now go through the entire array (NOT using the 'tempLen' value)
130          for (int ix=0; ix<data.length; ix++)
131          {
132             int value = (data[ix]>255)? -1: codes[ data[ix] ];
133    
134             if ( value >= 0 )           // skip over non-code
135             {
136                accum <<= 6;            // bits shift up by 6 each time thru
137                shift += 6;             // loop, with new bits being put in
138                accum |= value;         // at the bottom.
139                if ( shift >= 8 )       // whenever there are 8 or more shifted in,
140                {
141                   shift -= 8;         // write them out (from the top, leaving any
142                   out[index++] =      // excess at the bottom for next iteration.
143                      (byte) ((accum >> shift) & 0xff);
144                }
145             }
146             // we will also have skipped processing a padding null byte ('=') here;
147             // these are used ONLY for padding to an even length and do not legally
148             // occur as encoded data. for this reason we can ignore the fact that
149             // no index++ operation occurs in that special case: the out[] array is
150             // initialized to all-zero bytes to start with and that works to our
151             // advantage in this combination.
152          }
153    
154          // if there is STILL something wrong we just have to throw up now!
155          if( index != out.length)
156          {
157             throw new Error("Miscalculated data length (wrote " + index + " instead of " + out.length + ")");
158          }
159    
160          return out;
161       }
162    
163       public static byte[] decode(String data) {
164          return decode(data.toCharArray());
165       }
166    
167       //
168       // code characters for values 0..63
169       //
170       static private char[] alphabet =
171       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
172       .toCharArray();
173    
174       //
175       // lookup table for converting base64 characters to value in range 0..63
176       //
177       static private byte[] codes = new byte[256];
178       static {
179          for (int i=0; i<256; i++) codes[i] = -1;
180          for (int i = 'A'; i <= 'Z'; i++) codes[i] = (byte)(     i - 'A');
181          for (int i = 'a'; i <= 'z'; i++) codes[i] = (byte)(26 + i - 'a');
182          for (int i = '0'; i <= '9'; i++) codes[i] = (byte)(52 + i - '0');
183          codes['+'] = 62;
184          codes['/'] = 63;
185       }
186    
187    
188    
189    
190       ///////////////////////////////////////////////////
191       // remainder (main method and helper functions) is
192       // for testing purposes only, feel free to clip it.
193       ///////////////////////////////////////////////////
194    
195       public static void main(String[] args)
196       {
197          boolean decode = false;
198    
199          if (args.length == 0) {
200             System.out.println("usage:  java Base64 [-d[ecode]] filename");
201             System.exit(0);
202          }
203          for (int i=0; i<args.length; i++) {
204             if ("-decode".equalsIgnoreCase(args[i])) decode = true;
205             else if ("-d".equalsIgnoreCase(args[i])) decode = true;
206          }
207    
208          String filename = args[args.length-1];
209          File file = new File(filename);
210          if (!file.exists()) {
211             System.out.println("Error:  file '" + filename + "' doesn't exist!");
212             System.exit(0);
213          }
214    
215          if (decode)
216          {
217             char[] encoded = readChars(file);
218             byte[] decoded = decode(encoded);
219             writeBytes(file, decoded);
220          }
221          else
222          {
223             byte[] decoded = readBytes(file);
224             char[] encoded = encode(decoded);
225             writeChars(file, encoded);
226          }
227       }
228    
229       private static byte[] readBytes(File file)
230       {
231          ByteArrayOutputStream baos = new ByteArrayOutputStream();
232          try
233          {
234             InputStream fis = new FileInputStream(file);
235             InputStream is = new BufferedInputStream(fis);
236             int count = 0;
237             byte[] buf = new byte[16384];
238             while ((count=is.read(buf)) != -1) {
239                if (count > 0) baos.write(buf, 0, count);
240             }
241             is.close();
242          }
243          catch (Exception e) { e.printStackTrace(); }
244    
245          return baos.toByteArray();
246       }
247    
248       private static char[] readChars(File file)
249       {
250          CharArrayWriter caw = new CharArrayWriter();
251          try
252          {
253             Reader fr = new FileReader(file);
254             Reader in = new BufferedReader(fr);
255             int count = 0;
256             char[] buf = new char[16384];
257             while ((count=in.read(buf)) != -1) {
258                if (count > 0) caw.write(buf, 0, count);
259             }
260             in.close();
261          }
262          catch (Exception e) { e.printStackTrace(); }
263    
264          return caw.toCharArray();
265       }
266    
267       private static void writeBytes(File file, byte[] data) {
268          try {
269             OutputStream fos = new FileOutputStream(file);
270             OutputStream os = new BufferedOutputStream(fos);
271             os.write(data);
272             os.close();
273          }
274          catch (Exception e) { e.printStackTrace(); }
275       }
276    
277       private static void writeChars(File file, char[] data) {
278          try {
279             Writer fos = new FileWriter(file);
280             Writer os = new BufferedWriter(fos);
281             os.write(data);
282             os.close();
283          }
284          catch (Exception e) { e.printStackTrace(); }
285       }
286       ///////////////////////////////////////////////////
287       // end of test code.
288       ///////////////////////////////////////////////////
289    
290    }