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