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 © 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 }