1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.james.mime4j.codec;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27
28
29
30
31 public class Base64InputStream extends InputStream {
32 private static Log log = LogFactory.getLog(Base64InputStream.class);
33
34 private static final int ENCODED_BUFFER_SIZE = 1536;
35
36 private static final int[] BASE64_DECODE = new int[256];
37
38 static {
39 for (int i = 0; i < 256; i++)
40 BASE64_DECODE[i] = -1;
41 for (int i = 0; i < Base64OutputStream.BASE64_TABLE.length; i++)
42 BASE64_DECODE[Base64OutputStream.BASE64_TABLE[i] & 0xff] = i;
43 }
44
45 private static final byte BASE64_PAD = '=';
46
47 private static final int EOF = -1;
48
49 private final byte[] singleByte = new byte[1];
50
51 private boolean strict;
52
53 private final InputStream in;
54 private boolean closed = false;
55
56 private final byte[] encoded = new byte[ENCODED_BUFFER_SIZE];
57 private int position = 0;
58 private int size = 0;
59
60 private final ByteQueue q = new ByteQueue();
61
62 private boolean eof;
63
64 public Base64InputStream(InputStream in) {
65 this(in, false);
66 }
67
68 public Base64InputStream(InputStream in, boolean strict) {
69 if (in == null)
70 throw new IllegalArgumentException();
71
72 this.in = in;
73 this.strict = strict;
74 }
75
76 @Override
77 public int read() throws IOException {
78 if (closed)
79 throw new IOException("Base64InputStream has been closed");
80
81 while (true) {
82 int bytes = read0(singleByte, 0, 1);
83 if (bytes == EOF)
84 return EOF;
85
86 if (bytes == 1)
87 return singleByte[0] & 0xff;
88 }
89 }
90
91 @Override
92 public int read(byte[] buffer) throws IOException {
93 if (closed)
94 throw new IOException("Base64InputStream has been closed");
95
96 if (buffer == null)
97 throw new NullPointerException();
98
99 if (buffer.length == 0)
100 return 0;
101
102 return read0(buffer, 0, buffer.length);
103 }
104
105 @Override
106 public int read(byte[] buffer, int offset, int length) throws IOException {
107 if (closed)
108 throw new IOException("Base64InputStream has been closed");
109
110 if (buffer == null)
111 throw new NullPointerException();
112
113 if (offset < 0 || length < 0 || offset + length > buffer.length)
114 throw new IndexOutOfBoundsException();
115
116 if (length == 0)
117 return 0;
118
119 return read0(buffer, offset, offset + length);
120 }
121
122 @Override
123 public void close() throws IOException {
124 if (closed)
125 return;
126
127 closed = true;
128 }
129
130 private int read0(final byte[] buffer, final int from, final int to)
131 throws IOException {
132 int index = from;
133
134
135
136 int qCount = q.count();
137 while (qCount-- > 0 && index < to) {
138 buffer[index++] = q.dequeue();
139 }
140
141
142
143 if (eof)
144 return index == from ? EOF : index - from;
145
146
147
148 int data = 0;
149 int sextets = 0;
150
151 while (index < to) {
152
153
154 while (position == size) {
155 int n = in.read(encoded, 0, encoded.length);
156 if (n == EOF) {
157 eof = true;
158
159 if (sextets != 0) {
160
161 handleUnexpectedEof(sextets);
162 }
163
164 return index == from ? EOF : index - from;
165 } else if (n > 0) {
166 position = 0;
167 size = n;
168 } else {
169 assert n == 0;
170 }
171 }
172
173
174
175 while (position < size && index < to) {
176 int value = encoded[position++] & 0xff;
177
178 if (value == BASE64_PAD) {
179 index = decodePad(data, sextets, buffer, index, to);
180 return index - from;
181 }
182
183 int decoded = BASE64_DECODE[value];
184 if (decoded < 0)
185 continue;
186
187 data = (data << 6) | decoded;
188 sextets++;
189
190 if (sextets == 4) {
191 sextets = 0;
192
193 byte b1 = (byte) (data >>> 16);
194 byte b2 = (byte) (data >>> 8);
195 byte b3 = (byte) data;
196
197 if (index < to - 2) {
198 buffer[index++] = b1;
199 buffer[index++] = b2;
200 buffer[index++] = b3;
201 } else {
202 if (index < to - 1) {
203 buffer[index++] = b1;
204 buffer[index++] = b2;
205 q.enqueue(b3);
206 } else if (index < to) {
207 buffer[index++] = b1;
208 q.enqueue(b2);
209 q.enqueue(b3);
210 } else {
211 q.enqueue(b1);
212 q.enqueue(b2);
213 q.enqueue(b3);
214 }
215
216 assert index == to;
217 return to - from;
218 }
219 }
220 }
221 }
222
223 assert sextets == 0;
224 assert index == to;
225 return to - from;
226 }
227
228 private int decodePad(int data, int sextets, final byte[] buffer,
229 int index, final int end) throws IOException {
230 eof = true;
231
232 if (sextets == 2) {
233
234
235 byte b = (byte) (data >>> 4);
236 if (index < end) {
237 buffer[index++] = b;
238 } else {
239 q.enqueue(b);
240 }
241 } else if (sextets == 3) {
242
243
244 byte b1 = (byte) (data >>> 10);
245 byte b2 = (byte) ((data >>> 2) & 0xFF);
246
247 if (index < end - 1) {
248 buffer[index++] = b1;
249 buffer[index++] = b2;
250 } else if (index < end) {
251 buffer[index++] = b1;
252 q.enqueue(b2);
253 } else {
254 q.enqueue(b1);
255 q.enqueue(b2);
256 }
257 } else {
258
259 handleUnexpecedPad(sextets);
260 }
261
262 return index;
263 }
264
265 private void handleUnexpectedEof(int sextets) throws IOException {
266 if (strict)
267 throw new IOException("unexpected end of file");
268 else
269 log.warn("unexpected end of file; dropping " + sextets
270 + " sextet(s)");
271 }
272
273 private void handleUnexpecedPad(int sextets) throws IOException {
274 if (strict)
275 throw new IOException("unexpected padding character");
276 else
277 log.warn("unexpected padding character; dropping " + sextets
278 + " sextet(s)");
279 }
280 }