View Javadoc

1   /*
2   Copyright (C) 2000 - 2007 Grid Systems, S.A.
3   
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2, as
6   published by the Free Software Foundation.
7   
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12  
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17  
18  package com.gridsystems;
19  
20  import java.io.ByteArrayInputStream;
21  import java.io.ByteArrayOutputStream;
22  import java.io.FilterInputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  
26  /**
27   * A class to decode Base64 streams and strings.
28   * See RFC 1521 section 5.2 for details of the Base64 algorithm.
29   * <p>
30   * This class can be used for decoding strings:
31   * <blockquote><pre>
32   * String encoded = "d2VibWFzdGVyOnRyeTJndWVTUw";
33   * String decoded = Base64Decoder.decode(encoded);
34   * </pre></blockquote>
35   * or for decoding streams:
36   * <blockquote><pre>
37   * InputStream in = new Base64Decoder(System.in);
38   * </pre></blockquote>
39   *
40   * @author <b>Jason Hunter</b>, Copyright &#169; 2000
41   * @version 1.0, 2000/06/11
42   *
43   * Modified by Rodrigo Ruiz for Internal Use in GridSystems
44   */
45  public class Base64Decoder extends FilterInputStream {
46  
47    /** Mapping between six-bit integers and chars. */
48    private static final char[] CHARS = {
49      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
50      'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
51      'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
52      'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
53      'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
54      'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
55      '8', '9', '+', '/'
56    };
57  
58    /** A mapping between char values and six-bit integers. */
59    private static final int[] INTS = new int[128];
60    static {
61      for (int i = 0; i < 64; i++) {
62        INTS[CHARS[i]] = i;
63      }
64    }
65  
66    /** Char count. */
67    private int charCount;
68    /** Carryover. */
69    private int carryOver;
70  
71    /**
72     * Constructs a new Base64 decoder that reads input from the given
73     * InputStream.
74     *
75     * @param in the input stream
76     */
77    public Base64Decoder(InputStream in) {
78      super(in);
79    }
80  
81    /**
82     * Returns the next decoded character from the stream, or -1 if
83     * end of stream was reached.
84     *
85     * @return  the decoded character, or -1 if the end of the
86     *      input stream is reached
87     * @exception IOException if an I/O error occurs
88     */
89    public int read() throws IOException {
90      try {
91        // Read the next non-whitespace character
92        int x;
93        do {
94          x = in.read();
95          if (x == -1) {
96            return -1;
97          }
98        } while (Character.isWhitespace((char) x));
99        charCount++;
100 
101       // The '=' sign is just padding
102       if (x == '=') {
103         return -1; // effective end of stream
104       }
105 
106       // Convert from raw form to 6-bit form
107       x = INTS[x];
108 
109       // Calculate which character we're decoding now
110       int mode = (charCount - 1) % 4;
111 
112       // First char save all six bits, go for another
113       if (mode == 0) {
114         carryOver = x & 63;
115         return read();
116       } else if (mode == 1) {
117         // Second char use previous six bits and first two new bits,
118         // save last four bits
119         int decoded = ((carryOver << 2) + (x >> 4)) & 255;
120         carryOver = x & 15;
121         return decoded;
122       } else if (mode == 2) {
123         // Third char use previous four bits and first four new bits,
124         // save last two bits
125         int decoded = ((carryOver << 4) + (x >> 2)) & 255;
126         carryOver = x & 3;
127         return decoded;
128       } else if (mode == 3) {
129         // Fourth char use previous two bits and all six new bits
130         int decoded = ((carryOver << 6) + x) & 255;
131         return decoded;
132       }
133     } catch (ArrayIndexOutOfBoundsException aioobe) {
134       throw new IOException("Corrupted Base64 stream");
135     }
136     return -1; // can't actually reach this line
137   }
138 
139   /**
140    * Reads decoded data into an array of bytes and returns the actual
141    * number of bytes read, or -1 if end of stream was reached.
142    *
143    * @param b the buffer into which the data is read
144    * @param off the start offset of the data
145    * @param len the maximum number of bytes to read
146    * @return  the actual number of bytes read, or -1 if the end of the
147    *      input stream is reached
148    * @exception IOException if an I/O error occurs
149    */
150   public int read(byte[] b, int off, int len) throws IOException {
151     // This could of course be optimized
152     int i;
153     for (i = 0; i < len; i++) {
154       int x = read();
155       if (x == -1 && i == 0) {  // an immediate -1 returns -1
156         return -1;
157       } else if (x == -1) {       // a later -1 returns the chars read so far
158         break;
159       }
160       b[off + i] = (byte) x;
161     }
162     return i;
163   }
164 
165   /**
166    * Returns the decoded form of the given encoded string.
167    *
168    * @param encoded the string to decode
169    * @return the decoded form of the encoded string
170    */
171   public static String decode(String encoded) {
172     byte[] bytes = null;
173     // No need for specifying UTF-8, as encoded uses only ASCII characters.
174     bytes = encoded.getBytes();
175 
176     Base64Decoder in = new Base64Decoder(new ByteArrayInputStream(bytes));
177 
178     ByteArrayOutputStream out = new ByteArrayOutputStream((int) (bytes.length * 0.67));
179 
180     try {
181       byte[] buf = new byte[4 * 1024];  // 4K buffer
182       int bytesRead;
183       while ((bytesRead = in.read(buf)) != -1) {
184         out.write(buf, 0, bytesRead);
185       }
186       out.close();
187 
188       return out.toString("UTF-8");
189     } catch (IOException ignored) { return null; }
190   }
191 
192   /**
193    * Returns the decoded form of the given encoded byte array.
194    *
195    * @param encoded the byte array to decode
196    * @return the decoded form of encoded
197    */
198   public static byte[] binaryDecode(byte[] encoded) {
199     Base64Decoder in = new Base64Decoder(new ByteArrayInputStream(encoded));
200 
201     ByteArrayOutputStream out = new ByteArrayOutputStream((int) (encoded.length * 0.67));
202 
203     try {
204       byte[] buf = new byte[4 * 1024];  // 4K buffer
205       int bytesRead;
206       while ((bytesRead = in.read(buf)) != -1) {
207         out.write(buf, 0, bytesRead);
208       }
209       out.close();
210 
211       return out.toByteArray();
212     } catch (IOException ignored) { return new byte[0]; }
213   }
214 
215 }